From 5e20fae5dbe875c12bb0b866d56cefcb37c62dd5 Mon Sep 17 00:00:00 2001 From: taoshengshi Date: Fri, 24 Nov 2023 11:10:29 +0800 Subject: [PATCH 001/210] Initial commit --- .gitignore | 21 +++++++++++++++++++++ LICENSE | 21 +++++++++++++++++++++ README.md | 2 ++ 3 files changed, 44 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..3b735ec4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..8a897585 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 JiaoziFS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..ca368e74 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# jiaozifs +version control file system. From 764145eca25eaba6b6a3933ff4abcd7794469cb5 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Sun, 26 Nov 2023 10:01:57 +0800 Subject: [PATCH 002/210] feat: add docs --- docs/refrences.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 docs/refrences.md diff --git a/docs/refrences.md b/docs/refrences.md new file mode 100644 index 00000000..a8ba732a --- /dev/null +++ b/docs/refrences.md @@ -0,0 +1,26 @@ +一些参考的资料和引用 + +1. lakefs实现 https://github.com/treeverse/lakeFS +2. git内部原理 https://git-scm.com/book/zh/v2/Git-%E5%86%85%E9%83%A8%E5%8E%9F%E7%90%86-%E5%BA%95%E5%B1%82%E5%91%BD%E4%BB%A4%E4%B8%8E%E4%B8%8A%E5%B1%82%E5%91%BD%E4%BB%A4 +3. go-git代码实现 https://github.com/go-git/go-git +4. gitlab实现 https://github.com/gitlabhq/gitlabhq +5. https://github.com/dolthub + + + +git设计 + +1. objects + 数据对象 100644普通文件/100755可执行文件/120000符号链接 + 树对象 + 提交对象 + +2. refs + HEAD .git/HEAD + 引用 .git/refs/heads + 标签引用 .git/refs/tags + 远程引用 .git/refs/remotes + +3. 包文件 压缩上述文件到包里面 + +4. 传输协议 smart http/ssh \ No newline at end of file From df072e25a945232ba8c4a08e5b2c8250daf9705d Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Sun, 26 Nov 2023 15:18:19 +0800 Subject: [PATCH 003/210] jiaozifs backend --- docs/jiaozifs_mind.drawio | 413 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 413 insertions(+) create mode 100644 docs/jiaozifs_mind.drawio diff --git a/docs/jiaozifs_mind.drawio b/docs/jiaozifs_mind.drawio new file mode 100644 index 00000000..0b4a25f9 --- /dev/null +++ b/docs/jiaozifs_mind.drawio @@ -0,0 +1,413 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 7b0f4afc6bfde57b2191ac6d6af2f2f8834ac7d2 Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Sun, 26 Nov 2023 15:18:39 +0800 Subject: [PATCH 004/210] jiaozifs backend --- docs/jiaozifs_mind.drawio | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/jiaozifs_mind.drawio b/docs/jiaozifs_mind.drawio index 0b4a25f9..b7ca8f52 100644 --- a/docs/jiaozifs_mind.drawio +++ b/docs/jiaozifs_mind.drawio @@ -1,4 +1,4 @@ - + From be5a5fc32754e47a63f9c749d71f33796bc82b8b Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Sun, 26 Nov 2023 15:19:51 +0800 Subject: [PATCH 005/210] =?UTF-8?q?=E6=9B=B4=E6=96=B0jiaozifs=5Fmind.drawi?= =?UTF-8?q?o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/jiaozifs_mind.drawio | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/jiaozifs_mind.drawio b/docs/jiaozifs_mind.drawio index b7ca8f52..e1a8a73c 100644 --- a/docs/jiaozifs_mind.drawio +++ b/docs/jiaozifs_mind.drawio @@ -1,4 +1,4 @@ - + From c40a832ea70a8a9552cdda236daf303ee1c666d5 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Sun, 26 Nov 2023 15:20:29 +0800 Subject: [PATCH 006/210] test --- test.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 test.txt diff --git a/test.txt b/test.txt new file mode 100644 index 00000000..d969f6c5 --- /dev/null +++ b/test.txt @@ -0,0 +1,2 @@ +aa +aa From a6f9f0ca1cde7e50c6818781cb6806056679b8e5 Mon Sep 17 00:00:00 2001 From: taoshengshi Date: Sun, 26 Nov 2023 23:45:52 +0800 Subject: [PATCH 007/210] add notes for the jiaozifs,etc. --- .gitignore | 3 ++ docs/notes.md | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 docs/notes.md diff --git a/.gitignore b/.gitignore index 3b735ec4..7a71f53a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ *.so *.dylib +# goland +*.idea + # Test binary, built with `go test -c` *.test diff --git a/docs/notes.md b/docs/notes.md new file mode 100644 index 00000000..5ce88947 --- /dev/null +++ b/docs/notes.md @@ -0,0 +1,79 @@ +# 初心 + +GitData:IT & 基础设施生涯 的最后一件事情 + + +# Why + +Business Considerations Before Implementing AI | Technology Solutions | CompTIA +https://connect.comptia.org/content/guides/business-considerations-before-implementing-ai#:~:text=AI%20projects%20typically%20take%20anywhere,can%20build%20an%20AI%20algorithm. + +Conducted in October 2019, 63% of the 745 respondents have already developed and deployed a machine learning model into production. On average, 40% of companies said it takes more than a month to deploy an ML model into production, 28% do so in eight to 30 days, while only 14% could do so in seven days or less. + +Add It Up: How Long Does a Machine Learning Deployment Take? - The New Stack +https://thenewstack.io/add-it-up-how-long-does-a-machine-learning-deployment-take/ + +How to put machine learning models into production - Stack Overflow +https://stackoverflow.blog/2020/10/12/how-to-put-machine-learning-models-into-production/ + +## 行业报告 +https://docs.google.com/presentation/d/156WpBF_rGvf4Ecg19oM1fyR51g4FAmHV3Zs0WLukrLQ/edit#slide=id.g24daeb7f4f0_0_4995 +Welcome to State of AI Report 2023 +https://www.stateof.ai/ +https://docs.google.com/presentation/d/156WpBF_rGvf4Ecg19oM1fyR51g4FAmHV3Zs0WLukrLQ/edit#slide=id.g24daeb7f4f0_0_3373 + +us-ai-institute-state-of-ai-fifth-edition.pdf +https://www2.deloitte.com/content/dam/Deloitte/us/Documents/deloitte-analytics/us-ai-institute-state-of-ai-fifth-edition.pdf +AI in the Enterprise 2023 Report Analysis +https://4625340.fs1.hubspotusercontent-na1.net/hubfs/4625340/AI%20in%20the%20Enterprise%202023%20Report%20Analysis%20(1)-1.pdf?__hstc=77250698.c2e92c9cd279ea7865de033c58899c47.1701011639777.1701011639777.1701011639777.1&__hssc=77250698.1.1701011639778&__hsfp=4098988196 + +AI Adoption in the Enterprise 2022 – O’Reilly +https://www.oreilly.com/radar/ai-adoption-in-the-enterprise-2022/ + + +# 竞品 + +## XetHub +https://about.xethub.com/ +We built a GitHub app that scales repos to 100 Terabytes : r/github +https://www.reddit.com/r/github/comments/17wvaeq/we_built_a_github_app_that_scales_repos_to_100/ + + +## InfuseAI +$4.3M | 3 yrs ago + +* InfuseAI is a company that focuses on optimizing artificial intelligence workflows in the technology sector. The company offers products that streamline machine learning operations, making it faster and more efficient for organizations to work with AI. Their services are primarily utilized in the medical, education, and manufacturing industries. It was founded in 2018 and is based in Taipei, Taiwan. +* demo: PrimeHub Feature Showcase - YouTube + https://www.youtube.com/watch?v=t_nXF3l1MOY +* PrimeHub in 1 minute - YouTube + https://www.youtube.com/watch?v=PL2J6KPijdw +* +创始人的一点采访: +Data Friends #1 — a day in the life of ML engineer ft. PinChun | by catcatcatcat | InfuseAI +https://blog.infuseai.io/data-friends-1-a-day-in-the-life-of-ml-engineer-ft-pinchun-a71e5c124e4e + + + +## Datafold - Automated testing for data engineers + +网站:https://www.datafold.com/ + +有两个核心技术: +* Data Diff - Data Reliability Platform - Datafold + https://www.datafold.com/data-diff +* Column-level Lineage - Data Reliability Platform - Datafold + https://www.datafold.com/column-level-lineage + + +## Open Source Data Science Collaboration - DagsHub +https://dagshub.com/ + + +## Home Page | Pachyderm +https://www.pachyderm.com/ + + +# 方法 + +The product approach to open source communities - Stack Overflow +https://stackoverflow.blog/2023/11/08/the-product-approach-to-open-source-communities/ From dc02ce663ce8daf54ddb9449272d0c5d1e807d7c Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Mon, 27 Nov 2023 12:34:40 +0800 Subject: [PATCH 008/210] feat:init project --- api/docs.go | 4 + api/swagger.yml | 101 ++++++++++++++++++++ fx_opt/logger.go | 17 ++++ fx_opt/options.go | 233 +++++++++++++++++++++++++++++++++++++++++++++ go.mod | 17 ++++ go.sum | 68 +++++++++++++ main.go | 1 + makefile | 21 ++++ version/version.go | 12 +++ 9 files changed, 474 insertions(+) create mode 100644 api/docs.go create mode 100644 api/swagger.yml create mode 100644 fx_opt/logger.go create mode 100644 fx_opt/options.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 makefile create mode 100644 version/version.go diff --git a/api/docs.go b/api/docs.go new file mode 100644 index 00000000..5a1cb102 --- /dev/null +++ b/api/docs.go @@ -0,0 +1,4 @@ +// Package apigen provides generated code for our OpenAPI +package apigen + +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.5.6 -package apigen -generate "types,client,chi-server,spec" -o jiaozifs.gen.go ./swagger.yml diff --git a/api/swagger.yml b/api/swagger.yml new file mode 100644 index 00000000..0ae7a8b2 --- /dev/null +++ b/api/swagger.yml @@ -0,0 +1,101 @@ +openapi: "3.0.0" + +info: + description: lakeFS HTTP API + title: lakeFS API + license: + name: "Apache 2.0" + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.0 + +servers: + - url: "/api/v1" + description: lakeFS server endpoint +security: + - jwt_token: [] +components: + securitySchemes: + jwt_token: + type: http + scheme: bearer + bearerFormat: JWT + parameters: + PaginationPrefix: + in: query + name: prefix + description: return items prefixed with this value + schema: + type: string + + responses: + NotFoundOrNoACL: + description: Group not found, or group found but has no ACL + content: + application/json: + schema: + $ref: "#/components/schemas/Pagination" + + schemas: + Pagination: + type: object + required: + - has_more + - max_per_page + - results + - next_offset + properties: + has_more: + type: boolean + description: Next page is available + next_offset: + type: string + description: Token used to retrieve the next page + results: + type: integer + minimum: 0 + description: Number of values found in the results + max_per_page: + type: integer + minimum: 0 + description: Maximal number of entries per page + Error: + type: object + required: + - message + properties: + message: + description: short message explaining the error + type: string +paths: + /setup_comm_prefs: + post: + tags: + - internal + operationId: setupCommPrefs + summary: setup communications preferences + security: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + responses: + 200: + description: communication preferences saved successfully + 409: + description: setup was already completed + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 412: + description: wrong setup state for this operation + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 420: + description: too many requests + default: + $ref: "#/components/schemas/Error" \ No newline at end of file diff --git a/fx_opt/logger.go b/fx_opt/logger.go new file mode 100644 index 00000000..207a63e1 --- /dev/null +++ b/fx_opt/logger.go @@ -0,0 +1,17 @@ +package fx_opt //nolint + +import ( + logging "github.com/ipfs/go-log/v2" + "go.uber.org/fx" +) + +var fxLog = logging.Logger("fx") +var _ fx.Printer = (*Logger)(nil) + +// Logger log for debug fx message +type Logger struct{} + +// Printf print fx log message to debug log +func (log Logger) Printf(msg string, arg ...interface{}) { + fxLog.Debugf(msg, arg...) +} diff --git a/fx_opt/options.go b/fx_opt/options.go new file mode 100644 index 00000000..17d34bba --- /dev/null +++ b/fx_opt/options.go @@ -0,0 +1,233 @@ +package fx_opt //nolint + +import ( + "context" + "fmt" + "reflect" + + "go.uber.org/fx" +) + +type Invoke int + +var invokeVal int + +type StopFunc func(context.Context) error + +func NextInvoke() Invoke { + preInvoke := invokeVal + invokeVal++ + return Invoke(preInvoke) +} + +func defaults() []Option { + return []Option{} +} + +// New builds and starts new Filecoin node +func New(ctx context.Context, opts ...Option) (StopFunc, error) { + settings := Settings{ + modules: map[interface{}]fx.Option{}, + invokes: make([]fx.Option, invokeVal), + } + + // apply module options in the right order + if err := Options(Options(defaults()...), Options(opts...))(&settings); err != nil { + return nil, fmt.Errorf("applying node options failed: %w", err) + } + + // gather constructors for fx.Options + ctors := make([]fx.Option, 0, len(settings.modules)) + for _, opt := range settings.modules { + ctors = append(ctors, opt) + } + + // fill holes in invokes for use in fx.Options + for i, opt := range settings.invokes { + if opt == nil { + settings.invokes[i] = fx.Options() + } + } + + app := fx.New( + fx.Logger(&Logger{}), + fx.Options(ctors...), + fx.Options(settings.invokes...), + ) + + // TODO: we probably should have a 'firewall' for Closing signal + // on this context, and implement closing logic through lifecycles + // correctly + if err := app.Start(ctx); err != nil { + return nil, fmt.Errorf("starting node: %w", err) + } + + return app.Stop, nil +} + +type Settings struct { + // modules is a map of constructors for DI + // + // In most cases the index will be a reflect. Type of element returned by + // the constructor, but for some 'constructors' it's hard to specify what's + // the return type should be (or the constructor returns fx group) + modules map[interface{}]fx.Option + + // invokes are separate from modules as they can't be referenced by return + // type, and must be applied in correct order + invokes []fx.Option +} + +// Option is a functional option which can be used with the New function to +// change how the node is constructed +// +// Options are applied in sequence +type Option func(*Settings) error + +// Options groups multiple options into one +func Options(opts ...Option) Option { + return func(s *Settings) error { + for _, opt := range opts { + if err := opt(s); err != nil { + return err + } + } + return nil + } +} + +// Error is a special option which returns an error when applied +func Error(err error) Option { + return func(_ *Settings) error { + return err + } +} + +func ApplyIf(check func(s *Settings) bool, opts ...Option) Option { + return func(s *Settings) error { + if check(s) { + return Options(opts...)(s) + } + return nil + } +} + +func If(b bool, opts ...Option) Option { + return ApplyIf(func(s *Settings) bool { + return b + }, opts...) +} + +// Override option changes constructor for a given type +func Override(typ, constructor interface{}) Option { + return func(s *Settings) error { + if i, ok := typ.(Invoke); ok { + s.invokes[i] = fx.Invoke(constructor) + return nil + } + + ctor := as(constructor, typ) + rt := reflect.TypeOf(typ).Elem() + + s.modules[rt] = fx.Provide(ctor) + return nil + } +} + +func Annotate(typ, constructor interface{}) Option { + return func(s *Settings) error { + s.modules[typ] = fx.Provide(constructor) + return nil + } +} + +func Unset(typ interface{}) Option { + return func(s *Settings) error { + if i, ok := typ.(Invoke); ok { + s.invokes[i] = nil + return nil + } + + rt := reflect.TypeOf(typ).Elem() + + delete(s.modules, rt) + return nil + } +} + +// From (*T) -> func(t T) T {return t} +func From(typ interface{}) interface{} { + rt := []reflect.Type{reflect.TypeOf(typ).Elem()} + ft := reflect.FuncOf(rt, rt, false) + return reflect.MakeFunc(ft, func(args []reflect.Value) (results []reflect.Value) { + return args + }).Interface() +} + +func FromVal[T any](v T) func() T { + return func() T { + return v + } +} + +// from go-ipfs +// as casts input constructor to a given interface (if a value is given, it +// wraps it into a constructor). +// +// Note: this method may look like a hack, and in fact it is one. +// This is here only because https://github.com/uber-go/fx/issues/673 wasn't +// released yet +// +// Note 2: when making changes here, make sure this method stays at +// 100% coverage. This makes it less likely it will be terribly broken +func as(in interface{}, as interface{}) interface{} { + outType := reflect.TypeOf(as) + + if outType.Kind() != reflect.Ptr { + panic("outType is not a pointer") + } + + inType := reflect.TypeOf(in) + + if inType.Kind() != reflect.Func || inType.AssignableTo(outType.Elem()) { + ctype := reflect.FuncOf(nil, []reflect.Type{outType.Elem()}, false) + + return reflect.MakeFunc(ctype, func(args []reflect.Value) (results []reflect.Value) { + out := reflect.New(outType.Elem()) + out.Elem().Set(reflect.ValueOf(in)) + + return []reflect.Value{out.Elem()} + }).Interface() + } + + ins := make([]reflect.Type, inType.NumIn()) + outs := make([]reflect.Type, inType.NumOut()) + + for i := range ins { + ins[i] = inType.In(i) + } + outs[0] = outType.Elem() + for i := range outs[1:] { + outs[i+1] = inType.Out(i + 1) + } + + ctype := reflect.FuncOf(ins, outs, false) + + return reflect.MakeFunc(ctype, func(args []reflect.Value) (results []reflect.Value) { + outs := reflect.ValueOf(in).Call(args) + + out := reflect.New(outType.Elem()) + if outs[0].Type().AssignableTo(outType.Elem()) { + // Out: Iface = In: *Struct; Out: Iface = In: OtherIface + out.Elem().Set(outs[0]) + } else { + // Out: Iface = &(In: Struct) + t := reflect.New(outs[0].Type()) + t.Elem().Set(outs[0]) + out.Elem().Set(t) + } + outs[0] = out.Elem() + + return outs + }).Interface() +} diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..09876746 --- /dev/null +++ b/go.mod @@ -0,0 +1,17 @@ +module github.com/jiaozifs/jiaozifs + +go 1.20 + +require ( + github.com/ipfs/go-log/v2 v2.5.1 + go.uber.org/fx v1.20.1 +) + +require ( + github.com/mattn/go-isatty v0.0.14 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/dig v1.17.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + go.uber.org/zap v1.23.0 // indirect + golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..93f8d4f7 --- /dev/null +++ b/go.sum @@ -0,0 +1,68 @@ +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= +github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= +go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= +go.uber.org/fx v1.20.1 h1:zVwVQGS8zYvhh9Xxcu4w1M6ESyeMzebzj2NbSayZ4Mk= +go.uber.org/fx v1.20.1/go.mod h1:iSYNbHf2y55acNCwCXKx7LbWb5WG1Bnue5RDXz1OREg= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= +go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/main.go b/main.go new file mode 100644 index 00000000..06ab7d0f --- /dev/null +++ b/main.go @@ -0,0 +1 @@ +package main diff --git a/makefile b/makefile new file mode 100644 index 00000000..c9993923 --- /dev/null +++ b/makefile @@ -0,0 +1,21 @@ +SHELL=/usr/bin/env bash + +all: build +.PHONY: all + +ldflags=-X=github.com/jiaozofs/jiaozifs/version.CurrentCommit=+git.$(subst -,.,$(shell git describe --always --match=NeVeRmAtCh --dirty 2>/dev/null || git rev-parse --short HEAD 2>/dev/null)) +ifneq ($(strip $(LDFLAGS)),) + ldflags+=-extldflags=$(LDFLAGS) +endif + +GOFLAGS+=-ldflags="$(ldflags)" + +install-go-swagger: + go install github.com/go-swagger/go-swagger/cmd/swagger@latest + +SWAGGER_ARG= +swagger-srv: + swagger serve $(SWAGGER_ARG) -F swagger ./api/swagger.yml + +build + go build $(GOFLAGS) -o jiaozifs diff --git a/version/version.go b/version/version.go new file mode 100644 index 00000000..1196d419 --- /dev/null +++ b/version/version.go @@ -0,0 +1,12 @@ +package version + +// CurrentCommit current program commit +var CurrentCommit string + +// BuildVersion program version +const BuildVersion = "0.0.1" + +// UserVersion return build version and current commit +func UserVersion() string { + return BuildVersion + CurrentCommit +} From 42c32373f7f9d5129a37bb058007cdb009ab22db Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Mon, 27 Nov 2023 13:37:05 +0800 Subject: [PATCH 009/210] feat: add cobra flag and config --- .gitignore | 1 + LICENSE | 21 --- api/swagger.yml | 74 ++------ cmd/daemon.go | 62 +++++++ cmd/root.go | 77 ++++++++ cmd/version.go | 32 ++++ config/config.go | 10 + go.mod | 27 ++- go.sum | 460 ++++++++++++++++++++++++++++++++++++++++++++++ main.go | 10 + makefile | 8 +- utils/shutdown.go | 34 ++++ 12 files changed, 730 insertions(+), 86 deletions(-) delete mode 100644 LICENSE create mode 100644 cmd/daemon.go create mode 100644 cmd/root.go create mode 100644 cmd/version.go create mode 100644 config/config.go create mode 100644 utils/shutdown.go diff --git a/.gitignore b/.gitignore index 7a71f53a..8fc6f5ff 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ *.dll *.so *.dylib +jiaozifs # goland *.idea diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 8a897585..00000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2023 JiaoziFS - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/api/swagger.yml b/api/swagger.yml index 0ae7a8b2..d781452a 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -19,45 +19,17 @@ components: type: http scheme: bearer bearerFormat: JWT - parameters: - PaginationPrefix: - in: query - name: prefix - description: return items prefixed with this value - schema: - type: string - - responses: - NotFoundOrNoACL: - description: Group not found, or group found but has no ACL - content: - application/json: - schema: - $ref: "#/components/schemas/Pagination" schemas: - Pagination: + VersionResult: type: object - required: - - has_more - - max_per_page - - results - - next_offset properties: - has_more: - type: boolean - description: Next page is available - next_offset: + version: type: string - description: Token used to retrieve the next page - results: - type: integer - minimum: 0 - description: Number of values found in the results - max_per_page: - type: integer - minimum: 0 - description: Maximal number of entries per page + description: program version + runtime_version: + type: string + description: runtime version Error: type: object required: @@ -67,35 +39,17 @@ components: description: short message explaining the error type: string paths: - /setup_comm_prefs: - post: + /config: + get: tags: - - internal - operationId: setupCommPrefs - summary: setup communications preferences - security: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/Error" + - common + operationId: getVersion + summary: return program and runtime version responses: 200: - description: communication preferences saved successfully - 409: - description: setup was already completed + description: program version content: application/json: schema: - $ref: "#/components/schemas/Error" - 412: - description: wrong setup state for this operation - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - 420: - description: too many requests - default: - $ref: "#/components/schemas/Error" \ No newline at end of file + $ref: "#/components/schemas/VersionResult" + diff --git a/cmd/daemon.go b/cmd/daemon.go new file mode 100644 index 00000000..ce3851be --- /dev/null +++ b/cmd/daemon.go @@ -0,0 +1,62 @@ +/* +Copyright © 2023 NAME HERE +*/ +package cmd + +import ( + "context" + + logging "github.com/ipfs/go-log/v2" + "github.com/jiaozifs/jiaozifs/config" + "github.com/jiaozifs/jiaozifs/fx_opt" + "github.com/jiaozifs/jiaozifs/utils" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var log = logging.Logger("main") + +// daemonCmd represents the daemon command +var daemonCmd = &cobra.Command{ + Use: "daemon", + Short: "daemon program of jiaozifs", + Long: ``, + RunE: func(cmd *cobra.Command, args []string) error { + cfg := config.Config{} + err := viper.Unmarshal(&cfg) + if err != nil { + return err + } + + err = logging.SetLogLevel("*", cfg.Log.Level) + if err != nil { + return err + } + + shutdown := make(utils.Shutdown) + stop, err := fx_opt.New(cmd.Context(), + fx_opt.Override(new(context.Context), cmd.Context()), + fx_opt.Override(new(utils.Shutdown), shutdown), + //config + fx_opt.Override(new(*config.Config), cfg), + //business + + //api + + ) + if err != nil { + return err + } + + go utils.CatchSig(cmd.Context(), shutdown) + + <-shutdown + log.Info("graceful shutdown") + return stop(cmd.Context()) + return nil + }, +} + +func init() { + rootCmd.AddCommand(daemonCmd) +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 00000000..9af4d238 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,77 @@ +/* +Copyright © 2023 githun.com/jiaozifs/jiaozifs +*/ +package cmd + +import ( + "fmt" + "os" + "path" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var cfgFile string + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "jiaozifs", + Short: "A brief description of your application", + Long: `A longer description that spans multiple lines and likely contains +examples and usage of using your application. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + // Uncomment the following line if your bare application + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.jiaozifs/config.yaml)") + + // Cobra also supports local flags, which will only run + // when this action is called directly. + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := os.UserHomeDir() + cobra.CheckErr(err) + + // Search config in home directory with name ".jiaozifs" (without extension). + viper.AddConfigPath(path.Join(home, ".jiaozifs")) + viper.SetConfigType("yaml") + viper.SetConfigName("config") + } + + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) + } +} diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 00000000..a8a33bcd --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,32 @@ +/* +Copyright © 2023 NAME HERE +*/ +package cmd + +import ( + "github.com/spf13/cobra" +) + +// versionCmd represents the version command +var versionCmd = &cobra.Command{ + Use: "version", + Short: "version of jiaozifs", + Long: `jiaozifs version`, + RunE: func(cmd *cobra.Command, args []string) error { + return nil + }, +} + +func init() { + rootCmd.AddCommand(versionCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // versionCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // versionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 00000000..d0419a7d --- /dev/null +++ b/config/config.go @@ -0,0 +1,10 @@ +package config + +type Config struct { + Path string `mapstructure:"config"` + Log LogConfig `mapstructure:"log"` +} + +type LogConfig struct { + Level string `mapstructure:"level"` +} diff --git a/go.mod b/go.mod index 09876746..118e942a 100644 --- a/go.mod +++ b/go.mod @@ -8,10 +8,29 @@ require ( ) require ( - github.com/mattn/go-isatty v0.0.14 // indirect - go.uber.org/atomic v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/sagikazarmark/locafero v0.3.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.10.0 // indirect + github.com/spf13/cast v1.5.1 // indirect + github.com/spf13/cobra v1.8.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.17.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + go.uber.org/atomic v1.9.0 // indirect go.uber.org/dig v1.17.0 // indirect - go.uber.org/multierr v1.6.0 // indirect + go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.23.0 // indirect - golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/text v0.13.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 93f8d4f7..eafebcfb 100644 --- a/go.sum +++ b/go.sum @@ -1,68 +1,528 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= +github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= +github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= go.uber.org/fx v1.20.1 h1:zVwVQGS8zYvhh9Xxcu4w1M6ESyeMzebzj2NbSayZ4Mk= go.uber.org/fx v1.20.1/go.mod h1:iSYNbHf2y55acNCwCXKx7LbWb5WG1Bnue5RDXz1OREg= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/main.go b/main.go index 06ab7d0f..84342d98 100644 --- a/main.go +++ b/main.go @@ -1 +1,11 @@ +/* +Copyright © 2023 githun.com/jiaozifs/jiaozifs + +*/ package main + +import "github.com/jiaozifs/jiaozifs/cmd" + +func main() { + cmd.Execute() +} diff --git a/makefile b/makefile index c9993923..b3028496 100644 --- a/makefile +++ b/makefile @@ -1,4 +1,7 @@ SHELL=/usr/bin/env bash +GOCMD=$(or $(shell which go), $(error "Missing dependency - no go in PATH")) + +GOGENERATE=$(GOCMD) generate all: build .PHONY: all @@ -10,6 +13,9 @@ endif GOFLAGS+=-ldflags="$(ldflags)" +gen-api: + $(GOGENERATE) ./api + install-go-swagger: go install github.com/go-swagger/go-swagger/cmd/swagger@latest @@ -17,5 +23,5 @@ SWAGGER_ARG= swagger-srv: swagger serve $(SWAGGER_ARG) -F swagger ./api/swagger.yml -build +build: go build $(GOFLAGS) -o jiaozifs diff --git a/utils/shutdown.go b/utils/shutdown.go new file mode 100644 index 00000000..227a9c4b --- /dev/null +++ b/utils/shutdown.go @@ -0,0 +1,34 @@ +package utils + +import ( + "context" + "os" + "os/signal" + "syscall" +) + +// Shutdown chan for catch signal to shutdown program +type Shutdown chan struct{} + +// CatchSig wait for sigquit sigterm sigint sighup sigsegv to stop program +func CatchSig(ctx context.Context, done Shutdown) { + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT, syscall.SIGSEGV) +LOOP: + for { + select { + case <-ctx.Done(): + break LOOP + case s := <-c: + switch s { + case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT: + break LOOP + case syscall.SIGHUP: + case syscall.SIGSEGV: + default: + break LOOP + } + } + } + done <- struct{}{} +} From 6a016e10a9ca9bd3648a5e96143754dafa4bd1d7 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Mon, 27 Nov 2023 16:33:40 +0800 Subject: [PATCH 010/210] feat: add cofig operation --- api/api_impl/common.go | 17 ++ api/docs.go | 4 +- api/jiaozifs.gen.go | 502 +++++++++++++++++++++++++++++++++++++++++ cmd/daemon.go | 54 ++++- cmd/init.go | 23 ++ cmd/root.go | 48 +--- config/config.go | 74 ++++++ config/default.go | 11 + go.mod | 20 +- go.sum | 74 ++++-- main.go | 6 +- 11 files changed, 762 insertions(+), 71 deletions(-) create mode 100644 api/api_impl/common.go create mode 100644 api/jiaozifs.gen.go create mode 100644 cmd/init.go create mode 100644 config/default.go diff --git a/api/api_impl/common.go b/api/api_impl/common.go new file mode 100644 index 00000000..db0e8296 --- /dev/null +++ b/api/api_impl/common.go @@ -0,0 +1,17 @@ +package api_impl + +import ( + "github.com/jiaozifs/jiaozifs/api" + "go.uber.org/fx" + "net/http" +) + +var _ api.ServerInterface = (*APIController)(nil) + +type APIController struct { + fx.In +} + +func (A APIController) GetVersion(w http.ResponseWriter, r *http.Request) { + +} diff --git a/api/docs.go b/api/docs.go index 5a1cb102..d4215c8b 100644 --- a/api/docs.go +++ b/api/docs.go @@ -1,4 +1,4 @@ // Package apigen provides generated code for our OpenAPI -package apigen +package api -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.5.6 -package apigen -generate "types,client,chi-server,spec" -o jiaozifs.gen.go ./swagger.yml +//go:generate go run github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen -package api -generate "types,client,chi-server,spec" -o jiaozifs.gen.go ./swagger.yml diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go new file mode 100644 index 00000000..80bf3100 --- /dev/null +++ b/api/jiaozifs.gen.go @@ -0,0 +1,502 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen/v2 version v2.0.0 DO NOT EDIT. +package api + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/go-chi/chi/v5" +) + +const ( + Jwt_tokenScopes = "jwt_token.Scopes" +) + +// VersionResult defines model for VersionResult. +type VersionResult struct { + // RuntimeVersion runtime version + RuntimeVersion *string `json:"runtime_version,omitempty"` + + // Version program version + Version *string `json:"version,omitempty"` +} + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // GetVersion request + GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetVersionRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewGetVersionRequest generates requests for GetVersion +func NewGetVersionRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/config") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // GetVersionWithResponse request + GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) +} + +type GetVersionResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *VersionResult +} + +// Status returns HTTPResponse.Status +func (r GetVersionResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetVersionResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// GetVersionWithResponse request returning *GetVersionResponse +func (c *ClientWithResponses) GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) { + rsp, err := c.GetVersion(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetVersionResponse(rsp) +} + +// ParseGetVersionResponse parses an HTTP response from a GetVersionWithResponse call +func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetVersionResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest VersionResult + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // return program and runtime version + // (GET /config) + GetVersion(w http.ResponseWriter, r *http.Request) +} + +// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. + +type Unimplemented struct{} + +// return program and runtime version +// (GET /config) +func (_ Unimplemented) GetVersion(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// GetVersion operation middleware +func (siw *ServerInterfaceWrapper) GetVersion(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetVersion(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{}) +} + +type ChiServerOptions struct { + BaseURL string + BaseRouter chi.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = chi.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/config", wrapper.GetVersion) + }) + + return r +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/3xSTWvbQBD9K2Lao6pV3JtuoSStSymhNunBmLBZj6V1tB/MjmJM0H8vs/JHQ5OetJp5", + "b77eewETXAwePSdoXiCZDp3Oz3ukZIP/hWnoWQKRQkRiizlNg2fr8OF5gklog8mQjZx/T4DiBCiBDxGh", + "gcRkfQtjCe9yI4WWtHufO54j4XGHhnMkoRnI8mEhS0xT7vb8wOEJc49H1IR0G8hphga+/15COW0shabs", + "pVXHHGGUutZvw78z9voJbxfFt+Xyrri+m0MJvTXoEwrU61zzOmrTYTGraihhoP5YNjVK7ff7Sud0FahV", + "R25SP+Zfbn4ubj7Nqrrq2PVyJ7bc46Xl1O18PLiq6qoWXIjodbTQwOccKiFq7vIhlAl+a1t5tpjlFDG1", + "7DLfQANfke/PxyZMMcg0gpvVtXxM8Iw+M3WMvTWZq3Zp0m8yjrw+Em6hgQ/q4ix1tJV67al83P8Ln2Ud", + "nNN0EEshD+SLE0j7TfGGy3SboFmJsV3wsB7/tgY0q1emWK3HtWRJ+Dn5psgToEC/icF6PoupdLTq+QrG", + "9fgnAAD//4U8UzdKAwAA", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/cmd/daemon.go b/cmd/daemon.go index ce3851be..972b7df2 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -5,13 +5,19 @@ package cmd import ( "context" - + "errors" + "github.com/go-chi/chi/v5" logging "github.com/ipfs/go-log/v2" + "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/api/api_impl" "github.com/jiaozifs/jiaozifs/config" "github.com/jiaozifs/jiaozifs/fx_opt" "github.com/jiaozifs/jiaozifs/utils" + middleware "github.com/oapi-codegen/nethttp-middleware" "github.com/spf13/cobra" - "github.com/spf13/viper" + "go.uber.org/fx" + "net" + "net/http" ) var log = logging.Logger("main") @@ -22,8 +28,7 @@ var daemonCmd = &cobra.Command{ Short: "daemon program of jiaozifs", Long: ``, RunE: func(cmd *cobra.Command, args []string) error { - cfg := config.Config{} - err := viper.Unmarshal(&cfg) + cfg, err := config.LoadConfig(cfgFile) if err != nil { return err } @@ -39,10 +44,10 @@ var daemonCmd = &cobra.Command{ fx_opt.Override(new(utils.Shutdown), shutdown), //config fx_opt.Override(new(*config.Config), cfg), + fx_opt.Override(new(*config.APIConfig), &cfg.API), //business - //api - + fx_opt.Override(fx_opt.NextInvoke(), setupAPI), ) if err != nil { return err @@ -60,3 +65,40 @@ var daemonCmd = &cobra.Command{ func init() { rootCmd.AddCommand(daemonCmd) } + +func setupAPI(lc fx.Lifecycle, apiConfig *config.APIConfig, controller api_impl.APIController) error { + swagger, err := api.GetSwagger() + if err != nil { + return err + } + + // Clear out the servers array in the swagger spec, that skips validating + // that server names match. We don't know how this thing will be run. + swagger.Servers = nil + // This is how you set up a basic chi router + r := chi.NewRouter() + + // Use our validation middleware to check all requests against the + // OpenAPI schema. + r.Use(middleware.OapiRequestValidator(swagger)) + + api.HandlerFromMux(controller, r) + listener, err := net.Listen("tcp", apiConfig.Listen) + if err != nil { + return err + } + log.Infof("Start listen api %s", listener.Addr()) + go func() { + err := http.Serve(listener, r) + if err != nil && !errors.Is(err, http.ErrServerClosed) { + log.Errorf("listen address fail %s", err) + } + }() + + lc.Append(fx.Hook{ + OnStop: func(ctx context.Context) error { + return listener.Close() + }, + }) + return nil +} diff --git a/cmd/init.go b/cmd/init.go new file mode 100644 index 00000000..dd30f49a --- /dev/null +++ b/cmd/init.go @@ -0,0 +1,23 @@ +/* +Copyright © 2023 NAME HERE +*/ +package cmd + +import ( + "github.com/jiaozifs/jiaozifs/config" + "github.com/spf13/cobra" +) + +// initCmd represents the init command +var initCmd = &cobra.Command{ + Use: "init", + Short: "init jiaozifs ", + Long: `create default config file for jiaoozifs`, + RunE: func(cmd *cobra.Command, args []string) error { + return config.InitConfig() + }, +} + +func init() { + rootCmd.AddCommand(initCmd) +} diff --git a/cmd/root.go b/cmd/root.go index 9af4d238..57cb0563 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -4,12 +4,8 @@ Copyright © 2023 githun.com/jiaozifs/jiaozifs package cmd import ( - "fmt" - "os" - "path" - "github.com/spf13/cobra" - "github.com/spf13/viper" + "os" ) var cfgFile string @@ -17,13 +13,8 @@ var cfgFile string // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "jiaozifs", - Short: "A brief description of your application", - Long: `A longer description that spans multiple lines and likely contains -examples and usage of using your application. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, + Short: "version file for manage datasets", + Long: ``, // Uncomment the following line if your bare application // has an action associated with it: // Run: func(cmd *cobra.Command, args []string) { }, @@ -39,39 +30,6 @@ func Execute() { } func init() { - cobra.OnInitialize(initConfig) - - // Here you will define your flags and configuration settings. - // Cobra supports persistent flags, which, if defined here, - // will be global for your application. - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.jiaozifs/config.yaml)") - - // Cobra also supports local flags, which will only run - // when this action is called directly. rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } - -// initConfig reads in config file and ENV variables if set. -func initConfig() { - if cfgFile != "" { - // Use config file from the flag. - viper.SetConfigFile(cfgFile) - } else { - // Find home directory. - home, err := os.UserHomeDir() - cobra.CheckErr(err) - - // Search config in home directory with name ".jiaozifs" (without extension). - viper.AddConfigPath(path.Join(home, ".jiaozifs")) - viper.SetConfigType("yaml") - viper.SetConfigName("config") - } - - viper.AutomaticEnv() // read in environment variables that match - - // If a config file is found, read it in. - if err := viper.ReadInConfig(); err == nil { - fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) - } -} diff --git a/config/config.go b/config/config.go index d0419a7d..a92afcf4 100644 --- a/config/config.go +++ b/config/config.go @@ -1,10 +1,84 @@ package config +import ( + "fmt" + logging "github.com/ipfs/go-log/v2" + ms "github.com/mitchellh/mapstructure" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "os" + "path" +) + +var log = logging.Logger("log") + type Config struct { Path string `mapstructure:"config"` Log LogConfig `mapstructure:"log"` + API APIConfig `mapstructure:"api"` } type LogConfig struct { Level string `mapstructure:"level"` } + +type APIConfig struct { + Listen string `mapstructure:"level"` +} + +func InitConfig() error { + // Find home directory. + home, err := os.UserHomeDir() + cobra.CheckErr(err) + jiaoziHome := path.Join(home, ".jiaozifs") + defaultPath := path.Join(jiaoziHome, "config.toml") + + // Search config in home directory with name ".jiaozifs" (without extension). + viper.AddConfigPath(path.Join(home, ".jiaozifs")) + viper.SetConfigType("toml") + viper.SetConfigName("config") + if len(viper.ConfigFileUsed()) == 0 { + data := make(map[string]interface{}) + err = ms.Decode(defaultCfg, &data) + if err != nil { + return err + } + for k, v := range data { + viper.SetDefault(k, v) + } + err = os.MkdirAll(jiaoziHome, 0777) + if err != nil { + return err + } + return viper.WriteConfigAs(defaultPath) + } + return fmt.Errorf("config already exit in %s", defaultPath) +} + +// LoadConfig reads in config file and ENV variables if set. +func LoadConfig(cfgFile string) (*Config, error) { + if len(cfgFile) > 0 { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := os.UserHomeDir() + cobra.CheckErr(err) + + // Search config in home directory with name ".jiaozifs" (without extension). + viper.AddConfigPath(path.Join(home, ".jiaozifs")) + viper.SetConfigType("toml") + viper.SetConfigName("config") + } + + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + err := viper.ReadInConfig() + if err != nil { + return nil, err + } + + cfg := &Config{} + return cfg, viper.Unmarshal(cfg) +} diff --git a/config/default.go b/config/default.go new file mode 100644 index 00000000..5e17a838 --- /dev/null +++ b/config/default.go @@ -0,0 +1,11 @@ +package config + +var defaultCfg = Config{ + Path: "~/.jiaozifs/config.toml", + Log: LogConfig{ + Level: "INFO", + }, + API: APIConfig{ + Listen: "0.0.0.0:34913", + }, +} diff --git a/go.mod b/go.mod index 118e942a..440a49da 100644 --- a/go.mod +++ b/go.mod @@ -3,34 +3,48 @@ module github.com/jiaozifs/jiaozifs go 1.20 require ( + github.com/deepmap/oapi-codegen/v2 v2.0.0 + github.com/getkin/kin-openapi v0.118.0 + github.com/go-chi/chi/v5 v5.0.10 github.com/ipfs/go-log/v2 v2.5.1 + github.com/oapi-codegen/nethttp-middleware v1.0.1 + github.com/spf13/cobra v1.8.0 + github.com/spf13/viper v1.17.0 go.uber.org/fx v1.20.1 ) require ( github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/swag v0.22.4 // indirect + github.com/gorilla/mux v1.8.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/invopop/yaml v0.2.0 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/perimeterx/marshmallow v1.1.4 // indirect github.com/sagikazarmark/locafero v0.3.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.10.0 // indirect github.com/spf13/cast v1.5.1 // indirect - github.com/spf13/cobra v1.8.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.17.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/dig v1.17.0 // indirect go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.23.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/mod v0.12.0 // indirect golang.org/x/sys v0.12.0 // indirect golang.org/x/text v0.13.0 // indirect + golang.org/x/tools v0.13.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index eafebcfb..ad5ce9b5 100644 --- a/go.sum +++ b/go.sum @@ -49,20 +49,37 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/deepmap/oapi-codegen/v2 v2.0.0 h1:3TS7w3r+XnjKFXcbFbc16pTWzfTy0OLPkCsutEHjWDA= +github.com/deepmap/oapi-codegen/v2 v2.0.0/go.mod h1:7zR+ZL3WzLeCkr2k8oWTxEa0v8y/F25ane0l6A5UjLA= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/getkin/kin-openapi v0.118.0 h1:z43njxPmJ7TaPpMSCQb7PN0dEYno4tyBPQcrFdHoLuM= +github.com/getkin/kin-openapi v0.118.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc= +github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= +github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -99,6 +116,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -117,6 +135,8 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -125,33 +145,52 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= +github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= +github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/oapi-codegen/nethttp-middleware v1.0.1 h1:ZWvwfnMU0eloHX1VEJmQscQm3741t0vCm0eSIie1NIo= +github.com/oapi-codegen/nethttp-middleware v1.0.1/go.mod h1:P7xtAvpoqNB+5obR9qRCeefH7YlXWSK3KgPs/9WB8tE= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55FuFkxqLw= +github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= @@ -177,11 +216,16 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -193,7 +237,6 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -203,8 +246,6 @@ go.uber.org/fx v1.20.1 h1:zVwVQGS8zYvhh9Xxcu4w1M6ESyeMzebzj2NbSayZ4Mk= go.uber.org/fx v1.20.1/go.mod h1:iSYNbHf2y55acNCwCXKx7LbWb5WG1Bnue5RDXz1OREg= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= @@ -243,6 +284,7 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -254,6 +296,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -345,10 +389,8 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -413,6 +455,8 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -507,13 +551,17 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/main.go b/main.go index 84342d98..cdb89a36 100644 --- a/main.go +++ b/main.go @@ -1,10 +1,12 @@ /* Copyright © 2023 githun.com/jiaozifs/jiaozifs - */ package main -import "github.com/jiaozifs/jiaozifs/cmd" +import ( + _ "github.com/deepmap/oapi-codegen/v2/pkg/codegen" + "github.com/jiaozifs/jiaozifs/cmd" +) func main() { cmd.Execute() From 4f2f746b80a4ee678e915a33b0d1e96678acad9f Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Mon, 27 Nov 2023 19:37:09 +0800 Subject: [PATCH 011/210] feat: make version command work --- api/api_impl/common.go | 10 ++ api/api_impl/utils.go | 22 +++ api/custom_responser.go | 1 + api/docs.go | 2 +- api/jiaozifs.gen.go | 35 ++--- api/swagger.yml | 15 +- api/template/chi-handler.tmpl | 50 ++++++ api/template/chi-interface.tmpl | 17 ++ api/template/chi-middleware.tmpl | 261 +++++++++++++++++++++++++++++++ cmd/daemon.go | 9 +- cmd/version.go | 33 ++++ config/config.go | 2 +- config/default.go | 2 +- go.mod | 3 +- go.sum | 1 + main.go | 1 + makefile | 4 +- 17 files changed, 430 insertions(+), 38 deletions(-) create mode 100644 api/api_impl/utils.go create mode 100644 api/custom_responser.go create mode 100644 api/template/chi-handler.tmpl create mode 100644 api/template/chi-interface.tmpl create mode 100644 api/template/chi-middleware.tmpl diff --git a/api/api_impl/common.go b/api/api_impl/common.go index db0e8296..e6ccc918 100644 --- a/api/api_impl/common.go +++ b/api/api_impl/common.go @@ -2,6 +2,7 @@ package api_impl import ( "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/version" "go.uber.org/fx" "net/http" ) @@ -13,5 +14,14 @@ type APIController struct { } func (A APIController) GetVersion(w http.ResponseWriter, r *http.Request) { + swagger, err := api.GetSwagger() + if err != nil { + writeError(w, err) + return + } + writeJson(w, api.VersionResult{ + ApiVersion: swagger.Info.Version, + Version: version.UserVersion(), + }) } diff --git a/api/api_impl/utils.go b/api/api_impl/utils.go new file mode 100644 index 00000000..a8d38568 --- /dev/null +++ b/api/api_impl/utils.go @@ -0,0 +1,22 @@ +package api_impl + +import ( + "encoding/json" + "net/http" +) + +func writeJson(w http.ResponseWriter, v interface{}) { + data, err := json.Marshal(v) + if err != nil { + writeError(w, err) + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(data) +} + +func writeError(w http.ResponseWriter, err error) { + w.WriteHeader(http.StatusOK) + w.Write([]byte(err.Error())) +} diff --git a/api/custom_responser.go b/api/custom_responser.go new file mode 100644 index 00000000..778f64ec --- /dev/null +++ b/api/custom_responser.go @@ -0,0 +1 @@ +package api diff --git a/api/docs.go b/api/docs.go index d4215c8b..57263c57 100644 --- a/api/docs.go +++ b/api/docs.go @@ -1,4 +1,4 @@ // Package apigen provides generated code for our OpenAPI package api -//go:generate go run github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen -package api -generate "types,client,chi-server,spec" -o jiaozifs.gen.go ./swagger.yml +//go:generate go run github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen -package api -templates ./template -generate "types,client,chi-server,spec" -o jiaozifs.gen.go ./swagger.yml diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 80bf3100..15e3cd00 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -20,17 +20,13 @@ import ( "github.com/go-chi/chi/v5" ) -const ( - Jwt_tokenScopes = "jwt_token.Scopes" -) - // VersionResult defines model for VersionResult. type VersionResult struct { - // RuntimeVersion runtime version - RuntimeVersion *string `json:"runtime_version,omitempty"` + // ApiVersion runtime version + ApiVersion string `json:"api_version"` // Version program version - Version *string `json:"version,omitempty"` + Version string `json:"version"` } // RequestEditorFn is the function signature for the RequestEditor callback function @@ -131,7 +127,7 @@ func NewGetVersionRequest(server string) (*http.Request, error) { return nil, err } - operationPath := fmt.Sprintf("/config") + operationPath := fmt.Sprintf("/version") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -256,7 +252,7 @@ func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) { // ServerInterface represents all server handlers. type ServerInterface interface { // return program and runtime version - // (GET /config) + // (GET /version) GetVersion(w http.ResponseWriter, r *http.Request) } @@ -265,7 +261,7 @@ type ServerInterface interface { type Unimplemented struct{} // return program and runtime version -// (GET /config) +// (GET /version) func (_ Unimplemented) GetVersion(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } @@ -283,8 +279,6 @@ type MiddlewareFunc func(http.Handler) http.Handler func (siw *ServerInterfaceWrapper) GetVersion(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetVersion(w, r) })) @@ -410,7 +404,7 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl } r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/config", wrapper.GetVersion) + r.Get(options.BaseURL+"/version", wrapper.GetVersion) }) return r @@ -419,14 +413,13 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/3xSTWvbQBD9K2Lao6pV3JtuoSStSymhNunBmLBZj6V1tB/MjmJM0H8vs/JHQ5OetJp5", - "b77eewETXAwePSdoXiCZDp3Oz3ukZIP/hWnoWQKRQkRiizlNg2fr8OF5gklog8mQjZx/T4DiBCiBDxGh", - "gcRkfQtjCe9yI4WWtHufO54j4XGHhnMkoRnI8mEhS0xT7vb8wOEJc49H1IR0G8hphga+/15COW0shabs", - "pVXHHGGUutZvw78z9voJbxfFt+Xyrri+m0MJvTXoEwrU61zzOmrTYTGraihhoP5YNjVK7ff7Sud0FahV", - "R25SP+Zfbn4ubj7Nqrrq2PVyJ7bc46Xl1O18PLiq6qoWXIjodbTQwOccKiFq7vIhlAl+a1t5tpjlFDG1", - "7DLfQANfke/PxyZMMcg0gpvVtXxM8Iw+M3WMvTWZq3Zp0m8yjrw+Em6hgQ/q4ix1tJV67al83P8Ln2Ud", - "nNN0EEshD+SLE0j7TfGGy3SboFmJsV3wsB7/tgY0q1emWK3HtWRJ+Dn5psgToEC/icF6PoupdLTq+QrG", - "9fgnAAD//4U8UzdKAwAA", + "H4sIAAAAAAAC/3yST2/UMBDFv0o0cAxxutx8q1CBlRCqaNVLVSHjTBOX+A/jSVdVle+OxtnNLoLl5H+/", + "57HnvVew0acYMHAG/QrZDuhNmd4hZRfDN8zTyLKRKCYkdliOTXLfnxdElh1mSy5xWQJNgZ3H6gDUwC8J", + "QUNmcqGHuYaz2kSxJ+PPa+caCH9NjrADfQ9H7vRJD6ss/nhCyzCLzoXH+HfF0fzEjzfV59vb6+ryegs1", + "jM5iyChoMF5uuUzGDlhtmhZqmGgEDQNzylqp3W7XmHLcROrVXpvVl+2Hq683V+82TdsM7Ef5NTse8Vhy", + "qba2Ai6atmmFiwmDSQ40vC9bNSTDQ2m8Oulcj8UZ8cXIZ7YdaPiEfLf2hDCnKM8RbtO2MtgYGAMvLqbR", + "2aJVT3m5dMmAzN4SPoKGN+oYErVPiPozHqW7//dRiDx5b+hFEoI8UagOkAld9Y/QmD6LxTZ6L56WKhlJ", + "CND3Z3xcgApDl6ILvPqlTHLq+QLmh/l3AAAA//+mze959wIAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index d781452a..53766353 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -11,23 +11,18 @@ info: servers: - url: "/api/v1" description: lakeFS server endpoint -security: - - jwt_token: [] components: - securitySchemes: - jwt_token: - type: http - scheme: bearer - bearerFormat: JWT - schemas: VersionResult: type: object + required: + - version + - api_version properties: version: type: string description: program version - runtime_version: + api_version: type: string description: runtime version Error: @@ -39,7 +34,7 @@ components: description: short message explaining the error type: string paths: - /config: + /version: get: tags: - common diff --git a/api/template/chi-handler.tmpl b/api/template/chi-handler.tmpl new file mode 100644 index 00000000..066e28ac --- /dev/null +++ b/api/template/chi-handler.tmpl @@ -0,0 +1,50 @@ +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{}) +} + +type ChiServerOptions struct { + BaseURL string + BaseRouter chi.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler { + return HandlerWithOptions(si, ChiServerOptions { + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, ChiServerOptions { + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler { +r := options.BaseRouter + +if r == nil { +r = chi.NewRouter() +} +if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } +} +{{if .}}wrapper := ServerInterfaceWrapper{ +Handler: si, +HandlerMiddlewares: options.Middlewares, +ErrorHandlerFunc: options.ErrorHandlerFunc, +} +{{end}} +{{range .}}r.Group(func(r chi.Router) { +r.{{.Method | lower | title }}(options.BaseURL+"{{.Path | swaggerUriToChiUri}}", wrapper.{{.OperationId}}) +}) +{{end}} +return r +} \ No newline at end of file diff --git a/api/template/chi-interface.tmpl b/api/template/chi-interface.tmpl new file mode 100644 index 00000000..cfc393bd --- /dev/null +++ b/api/template/chi-interface.tmpl @@ -0,0 +1,17 @@ +// ServerInterface represents all server handlers. +type ServerInterface interface { +{{range .}}{{.SummaryAsComment }} +// ({{.Method}} {{.Path}}) +{{.OperationId}}(w http.ResponseWriter, r *http.Request{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) +{{end}} +} + +// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. + +type Unimplemented struct {} + {{range .}}{{.SummaryAsComment }} + // ({{.Method}} {{.Path}}) + func (_ Unimplemented) {{.OperationId}}(w http.ResponseWriter, r *http.Request{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) { + w.WriteHeader(http.StatusNotImplemented) + } + {{end}} \ No newline at end of file diff --git a/api/template/chi-middleware.tmpl b/api/template/chi-middleware.tmpl new file mode 100644 index 00000000..3e9b325e --- /dev/null +++ b/api/template/chi-middleware.tmpl @@ -0,0 +1,261 @@ +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +{{range .}}{{$opid := .OperationId}} + +// {{$opid}} operation middleware +func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + {{if or .RequiresParamObject (gt (len .PathParams) 0) }} + var err error + {{end}} + + {{range .PathParams}}// ------------- Path parameter "{{.ParamName}}" ------------- + var {{$varName := .GoVariableName}}{{$varName}} {{.TypeDef}} + + {{if .IsPassThrough}} + {{$varName}} = chi.URLParam(r, "{{.ParamName}}") + {{end}} + {{if .IsJson}} + err = json.Unmarshal([]byte(chi.URLParam(r, "{{.ParamName}}")), &{{$varName}}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err}) + return + } + {{end}} + {{if .IsStyled}} + err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", chi.URLParam(r, "{{.ParamName}}"), &{{$varName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: {{.Explode}}, Required: {{.Required}}}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) + return + } + {{end}} + + {{end}} + +{{range .SecurityDefinitions}} + ctx = context.WithValue(ctx, {{.ProviderName | sanitizeGoIdentity | ucFirst}}Scopes, {{toStringArray .Scopes}}) +{{end}} + + {{if .RequiresParamObject}} + // Parameter object where we will unmarshal all parameters from the context + var params {{.OperationId}}Params + + {{range $paramIdx, $param := .QueryParams}} + {{- if (or (or .Required .IsPassThrough) (or .IsJson .IsStyled)) -}} + // ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" ------------- + {{ end }} + {{ if (or (or .Required .IsPassThrough) .IsJson) }} + if paramValue := r.URL.Query().Get("{{.ParamName}}"); paramValue != "" { + + {{if .IsPassThrough}} + params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue + {{end}} + + {{if .IsJson}} + var value {{.TypeDef}} + err = json.Unmarshal([]byte(paramValue), &value) + if err != nil { + siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err}) + return + } + + params.{{.GoName}} = {{if not .Required}}&{{end}}value + {{end}} + }{{if .Required}} else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"}) + return + }{{end}} + {{end}} + {{if .IsStyled}} + err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", r.URL.Query(), ¶ms.{{.GoName}}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) + return + } + {{end}} + {{end}} + + {{if .HeaderParams}} + headers := r.Header + + {{range .HeaderParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} header parameter "{{.ParamName}}" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("{{.ParamName}}")]; found { + var {{.GoName}} {{.TypeDef}} + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "{{.ParamName}}", Count: n}) + return + } + + {{if .IsPassThrough}} + params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0] + {{end}} + + {{if .IsJson}} + err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err}) + return + } + {{end}} + + {{if .IsStyled}} + err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", valueList[0], &{{.GoName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: {{.Explode}}, Required: {{.Required}}}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) + return + } + {{end}} + + params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}} + + } {{if .Required}}else { + err := fmt.Errorf("Header parameter {{.ParamName}} is required, but not found") + siw.ErrorHandlerFunc(w, r, &RequiredHeaderError{ParamName: "{{.ParamName}}", Err: err}) + return + }{{end}} + + {{end}} + {{end}} + + {{range .CookieParams}} + var cookie *http.Cookie + + if cookie, err = r.Cookie("{{.ParamName}}"); err == nil { + + {{- if .IsPassThrough}} + params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value + {{end}} + + {{- if .IsJson}} + var value {{.TypeDef}} + var decoded string + decoded, err := url.QueryUnescape(cookie.Value) + if err != nil { + err = fmt.Errorf("Error unescaping cookie parameter '{{.ParamName}}'") + siw.ErrorHandlerFunc(w, r, &UnescapedCookieParamError{ParamName: "{{.ParamName}}", Err: err}) + return + } + + err = json.Unmarshal([]byte(decoded), &value) + if err != nil { + siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err}) + return + } + + params.{{.GoName}} = {{if not .Required}}&{{end}}value + {{end}} + + {{- if .IsStyled}} + var value {{.TypeDef}} + err = runtime.BindStyledParameterWithOptions("simple", "{{.ParamName}}", cookie.Value, &value, runtime.BindStyledParameterOptions{Explode: {{.Explode}}, Required: {{.Required}}}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) + return + } + params.{{.GoName}} = {{if not .Required}}&{{end}}value + {{end}} + + } + + {{- if .Required}} else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"}) + return + } + {{- end}} + {{end}} + {{end}} + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.{{.OperationId}}(w, r{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}) + })) + + {{if opts.Compatibility.ApplyChiMiddlewareFirstToLast}} + for i := len(siw.HandlerMiddlewares) -1; i >= 0; i-- { + handler = siw.HandlerMiddlewares[i](handler) + } + {{else}} + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + {{end}} + + handler.ServeHTTP(w, r.WithContext(ctx)) +} +{{end}} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} \ No newline at end of file diff --git a/cmd/daemon.go b/cmd/daemon.go index 972b7df2..6e85c991 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -18,6 +18,7 @@ import ( "go.uber.org/fx" "net" "net/http" + "net/url" ) var log = logging.Logger("main") @@ -83,7 +84,13 @@ func setupAPI(lc fx.Lifecycle, apiConfig *config.APIConfig, controller api_impl. r.Use(middleware.OapiRequestValidator(swagger)) api.HandlerFromMux(controller, r) - listener, err := net.Listen("tcp", apiConfig.Listen) + + url, err := url.Parse(apiConfig.Listen) + if err != nil { + return err + } + + listener, err := net.Listen("tcp", url.Host) if err != nil { return err } diff --git a/cmd/version.go b/cmd/version.go index a8a33bcd..3d6df0e0 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -4,6 +4,10 @@ Copyright © 2023 NAME HERE package cmd import ( + "fmt" + "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/config" + "github.com/jiaozifs/jiaozifs/version" "github.com/spf13/cobra" ) @@ -13,6 +17,35 @@ var versionCmd = &cobra.Command{ Short: "version of jiaozifs", Long: `jiaozifs version`, RunE: func(cmd *cobra.Command, args []string) error { + cfg, err := config.LoadConfig(cfgFile) + if err != nil { + return err + } + client, err := api.NewClient(cfg.API.Listen) + if err != nil { + return err + } + + swagger, err := api.GetSwagger() + if err != nil { + return err + } + + versionResp, err := client.GetVersion(cmd.Context()) + if err != nil { + return err + } + okResp, err := api.ParseGetVersionResponse(versionResp) + if err != nil { + return err + } + if okResp.JSON200 == nil { + return fmt.Errorf("request version fail %d %s", okResp.HTTPResponse.StatusCode, okResp.HTTPResponse.Body) + } + fmt.Println("Version ", version.UserVersion()) + fmt.Println("API Version ", swagger.Info.Version) + fmt.Println("Runtime Version ", okResp.JSON200.Version) + fmt.Println("Runtime API Version ", okResp.JSON200.ApiVersion) return nil }, } diff --git a/config/config.go b/config/config.go index a92afcf4..fc7adf04 100644 --- a/config/config.go +++ b/config/config.go @@ -23,7 +23,7 @@ type LogConfig struct { } type APIConfig struct { - Listen string `mapstructure:"level"` + Listen string `mapstructure:"listen"` } func InitConfig() error { diff --git a/config/default.go b/config/default.go index 5e17a838..bfbb3263 100644 --- a/config/default.go +++ b/config/default.go @@ -6,6 +6,6 @@ var defaultCfg = Config{ Level: "INFO", }, API: APIConfig{ - Listen: "0.0.0.0:34913", + Listen: "http://127.0.0.1:34913", }, } diff --git a/go.mod b/go.mod index 440a49da..15e088d8 100644 --- a/go.mod +++ b/go.mod @@ -7,10 +7,12 @@ require ( github.com/getkin/kin-openapi v0.118.0 github.com/go-chi/chi/v5 v5.0.10 github.com/ipfs/go-log/v2 v2.5.1 + github.com/mitchellh/mapstructure v1.5.0 github.com/oapi-codegen/nethttp-middleware v1.0.1 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.17.0 go.uber.org/fx v1.20.1 + gopkg.in/yaml.v2 v2.4.0 ) require ( @@ -25,7 +27,6 @@ require ( github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.19 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/perimeterx/marshmallow v1.1.4 // indirect diff --git a/go.sum b/go.sum index ad5ce9b5..029f5d81 100644 --- a/go.sum +++ b/go.sum @@ -558,6 +558,7 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index cdb89a36..2d0a5ddf 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ package main import ( _ "github.com/deepmap/oapi-codegen/v2/pkg/codegen" "github.com/jiaozifs/jiaozifs/cmd" + _ "gopkg.in/yaml.v2" ) func main() { diff --git a/makefile b/makefile index b3028496..db114e43 100644 --- a/makefile +++ b/makefile @@ -13,7 +13,7 @@ endif GOFLAGS+=-ldflags="$(ldflags)" -gen-api: +gen-api: ./api/swagger.yml $(GOGENERATE) ./api install-go-swagger: @@ -23,5 +23,5 @@ SWAGGER_ARG= swagger-srv: swagger serve $(SWAGGER_ARG) -F swagger ./api/swagger.yml -build: +build:gen-api go build $(GOFLAGS) -o jiaozifs From d8c6bca0a3ec5d90f9a9792ce722c0984f878f0f Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Mon, 27 Nov 2023 20:20:34 +0800 Subject: [PATCH 012/210] feat: use custom response --- api/api_impl/common.go | 6 ++--- api/custom_responser.go | 25 +++++++++++++++++++ api/docs.go | 2 +- api/jiaozifs.gen.go | 12 ++++----- api/{template => tmpls/chi}/chi-handler.tmpl | 0 .../chi}/chi-interface.tmpl | 4 +-- .../chi}/chi-middleware.tmpl | 2 +- go.mod | 4 +-- go.sum | 8 +++--- makefile | 2 +- 10 files changed, 44 insertions(+), 21 deletions(-) rename api/{template => tmpls/chi}/chi-handler.tmpl (100%) rename api/{template => tmpls/chi}/chi-interface.tmpl (55%) rename api/{template => tmpls/chi}/chi-middleware.tmpl (98%) diff --git a/api/api_impl/common.go b/api/api_impl/common.go index e6ccc918..4f409c82 100644 --- a/api/api_impl/common.go +++ b/api/api_impl/common.go @@ -13,14 +13,14 @@ type APIController struct { fx.In } -func (A APIController) GetVersion(w http.ResponseWriter, r *http.Request) { +func (A APIController) GetVersion(w *api.JiaozifsResponse, r *http.Request) { swagger, err := api.GetSwagger() if err != nil { - writeError(w, err) + w.RespError(err) return } - writeJson(w, api.VersionResult{ + w.RespJSON(api.VersionResult{ ApiVersion: swagger.Info.Version, Version: version.UserVersion(), }) diff --git a/api/custom_responser.go b/api/custom_responser.go index 778f64ec..ede45c79 100644 --- a/api/custom_responser.go +++ b/api/custom_responser.go @@ -1 +1,26 @@ package api + +import ( + "encoding/json" + "net/http" +) + +type JiaozifsResponse struct { + http.ResponseWriter +} + +func (response *JiaozifsResponse) RespJSON(v interface{}) { + data, err := json.Marshal(v) + if err != nil { + response.RespError(err) + return + } + response.Header().Set("Content-Type", "application/json") + response.WriteHeader(http.StatusOK) + _, _ = response.Write(data) +} + +func (response *JiaozifsResponse) RespError(err error) { + response.WriteHeader(http.StatusOK) + response.Write([]byte(err.Error())) +} diff --git a/api/docs.go b/api/docs.go index 57263c57..290454b3 100644 --- a/api/docs.go +++ b/api/docs.go @@ -1,4 +1,4 @@ // Package apigen provides generated code for our OpenAPI package api -//go:generate go run github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen -package api -templates ./template -generate "types,client,chi-server,spec" -o jiaozifs.gen.go ./swagger.yml +//go:generate go run github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen -package api -templates ./tmpls -generate "types,client,chi-server,spec" -o jiaozifs.gen.go ./swagger.yml diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 15e3cd00..3b94daa9 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -1,6 +1,6 @@ // Package api provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen/v2 version v2.0.0 DO NOT EDIT. +// Code generated by github.com/deepmap/oapi-codegen/v2 version v2.0.1-0.20231120160225-add3126ee845 DO NOT EDIT. package api import ( @@ -253,7 +253,7 @@ func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) { type ServerInterface interface { // return program and runtime version // (GET /version) - GetVersion(w http.ResponseWriter, r *http.Request) + GetVersion(w *JiaozifsResponse, r *http.Request) } // Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. @@ -262,7 +262,7 @@ type Unimplemented struct{} // return program and runtime version // (GET /version) -func (_ Unimplemented) GetVersion(w http.ResponseWriter, r *http.Request) { +func (_ Unimplemented) GetVersion(w *JiaozifsResponse, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } @@ -280,7 +280,7 @@ func (siw *ServerInterfaceWrapper) GetVersion(w http.ResponseWriter, r *http.Req ctx := r.Context() handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetVersion(w, r) + siw.Handler.GetVersion(&JiaozifsResponse{w}, r) })) for _, middleware := range siw.HandlerMiddlewares { @@ -408,9 +408,7 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl }) return r -} - -// Base64 encoded, gzipped, json marshaled Swagger object +} // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ "H4sIAAAAAAAC/3yST2/UMBDFv0o0cAxxutx8q1CBlRCqaNVLVSHjTBOX+A/jSVdVle+OxtnNLoLl5H+/", diff --git a/api/template/chi-handler.tmpl b/api/tmpls/chi/chi-handler.tmpl similarity index 100% rename from api/template/chi-handler.tmpl rename to api/tmpls/chi/chi-handler.tmpl diff --git a/api/template/chi-interface.tmpl b/api/tmpls/chi/chi-interface.tmpl similarity index 55% rename from api/template/chi-interface.tmpl rename to api/tmpls/chi/chi-interface.tmpl index cfc393bd..a561c253 100644 --- a/api/template/chi-interface.tmpl +++ b/api/tmpls/chi/chi-interface.tmpl @@ -2,7 +2,7 @@ type ServerInterface interface { {{range .}}{{.SummaryAsComment }} // ({{.Method}} {{.Path}}) -{{.OperationId}}(w http.ResponseWriter, r *http.Request{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) +{{.OperationId}}(w *JiaozifsResponse, r *http.Request{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) {{end}} } @@ -11,7 +11,7 @@ type ServerInterface interface { type Unimplemented struct {} {{range .}}{{.SummaryAsComment }} // ({{.Method}} {{.Path}}) - func (_ Unimplemented) {{.OperationId}}(w http.ResponseWriter, r *http.Request{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) { + func (_ Unimplemented) {{.OperationId}}(w *JiaozifsResponse, r *http.Request{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) { w.WriteHeader(http.StatusNotImplemented) } {{end}} \ No newline at end of file diff --git a/api/template/chi-middleware.tmpl b/api/tmpls/chi/chi-middleware.tmpl similarity index 98% rename from api/template/chi-middleware.tmpl rename to api/tmpls/chi/chi-middleware.tmpl index 3e9b325e..78fa82ef 100644 --- a/api/template/chi-middleware.tmpl +++ b/api/tmpls/chi/chi-middleware.tmpl @@ -174,7 +174,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ {{end}} handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.{{.OperationId}}(w, r{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}) + siw.Handler.{{.OperationId}}(&JiaozifsResponse{w}, r{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}) })) {{if opts.Compatibility.ApplyChiMiddlewareFirstToLast}} diff --git a/go.mod b/go.mod index 15e088d8..4d754de4 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/jiaozifs/jiaozifs go 1.20 require ( - github.com/deepmap/oapi-codegen/v2 v2.0.0 + github.com/deepmap/oapi-codegen/v2 v2.0.1-0.20231120160225-add3126ee845 github.com/getkin/kin-openapi v0.118.0 github.com/go-chi/chi/v5 v5.0.10 github.com/ipfs/go-log/v2 v2.5.1 @@ -44,7 +44,7 @@ require ( golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/sys v0.12.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.13.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 029f5d81..63fc335f 100644 --- a/go.sum +++ b/go.sum @@ -53,8 +53,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/deepmap/oapi-codegen/v2 v2.0.0 h1:3TS7w3r+XnjKFXcbFbc16pTWzfTy0OLPkCsutEHjWDA= -github.com/deepmap/oapi-codegen/v2 v2.0.0/go.mod h1:7zR+ZL3WzLeCkr2k8oWTxEa0v8y/F25ane0l6A5UjLA= +github.com/deepmap/oapi-codegen/v2 v2.0.1-0.20231120160225-add3126ee845 h1:QlRkcr1t+VcHkdk8WJDhuiI94RqmjSgtflB3Q+H8X2k= +github.com/deepmap/oapi-codegen/v2 v2.0.1-0.20231120160225-add3126ee845/go.mod h1:pB9cROTwrn6Gj3Rtmcmp5fwV23znquC9tY1rR6+/R3s= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -402,8 +402,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/makefile b/makefile index db114e43..5dab8bbd 100644 --- a/makefile +++ b/makefile @@ -13,7 +13,7 @@ endif GOFLAGS+=-ldflags="$(ldflags)" -gen-api: ./api/swagger.yml +gen-api: ./api/swagger.yml ./api/tmpls/chi $(GOGENERATE) ./api install-go-swagger: From 47aa61275c77af20ea24c1cb84d2aedca63b33ab Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Tue, 28 Nov 2023 09:35:36 +0800 Subject: [PATCH 013/210] =?UTF-8?q?=E6=9B=B4=E6=96=B0jiaozifs=5Fmind.drawi?= =?UTF-8?q?o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/jiaozifs_mind.drawio | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/jiaozifs_mind.drawio b/docs/jiaozifs_mind.drawio index e1a8a73c..77af902e 100644 --- a/docs/jiaozifs_mind.drawio +++ b/docs/jiaozifs_mind.drawio @@ -1,4 +1,4 @@ - + From 6f84891c184e83f9b13a9d6e7945bd29559d4598 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Tue, 28 Nov 2023 18:28:30 +0800 Subject: [PATCH 014/210] feat: perform orm and cors function --- api/api_impl/server.go | 86 +++++++++++++++++++ ...custom_responser.go => custom_response.go} | 0 api/jiaozifs.gen.go | 22 +++-- api/swagger.yml | 32 ++++++- auth/basic_auth.go | 1 + cmd/daemon.go | 66 +++----------- cmd/init.go | 6 ++ cmd/root.go | 6 +- cmd/version.go | 24 ++---- config/config.go | 12 ++- go.mod | 11 +++ go.sum | 24 ++++++ .../migrations/20210505110026_init_project.go | 30 +++++++ models/migrations/main.go | 26 ++++++ models/models.go | 28 ++++++ models/user.go | 52 +++++++++++ 16 files changed, 340 insertions(+), 86 deletions(-) create mode 100644 api/api_impl/server.go rename api/{custom_responser.go => custom_response.go} (100%) create mode 100644 auth/basic_auth.go create mode 100644 models/migrations/20210505110026_init_project.go create mode 100644 models/migrations/main.go create mode 100644 models/models.go create mode 100644 models/user.go diff --git a/api/api_impl/server.go b/api/api_impl/server.go new file mode 100644 index 00000000..9f3c73c3 --- /dev/null +++ b/api/api_impl/server.go @@ -0,0 +1,86 @@ +package api_impl + +import ( + "context" + "errors" + "github.com/getkin/kin-openapi/openapi3filter" + "github.com/go-chi/chi/v5" + "github.com/go-chi/cors" + logging "github.com/ipfs/go-log/v2" + "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/config" + middleware "github.com/oapi-codegen/nethttp-middleware" + "go.uber.org/fx" + "net" + "net/http" + "net/url" +) + +var log = logging.Logger("rpc") + +const APIV1Prefix = "/api/v1" + +func SetupAPI(lc fx.Lifecycle, apiConfig *config.APIConfig, controller APIController) error { + swagger, err := api.GetSwagger() + if err != nil { + return err + } + + // Clear out the servers array in the swagger spec, that skips validating + // that server names match. We don't know how this thing will be run. + swagger.Servers = nil + // This is how you set up a basic chi router + r := chi.NewRouter() + + // Use our validation middleware to check all requests against the + // OpenAPI schema. + r.Use( + cors.Handler(cors.Options{ + // Basic CORS + // for more ideas, see: https://developer.github.com/v3/#cross-origin-resource-sharing + + // AllowedOrigins: []string{"https://foo.com"}, // Use this to allow specific origin hosts + AllowedOrigins: []string{"https://*", "http://*"}, + // AllowOriginFunc: func(r *http.Request, origin string) bool { return true }, + AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, + AllowedHeaders: []string{"*"}, + ExposedHeaders: []string{"*"}, + AllowCredentials: false, + MaxAge: 300, // Maximum value not ignored by any of major browsers + }), + + middleware.OapiRequestValidatorWithOptions(swagger, &middleware.Options{ + Options: openapi3filter.Options{ + AuthenticationFunc: func(ctx context.Context, input *openapi3filter.AuthenticationInput) error { + return nil + }, + }, + }), + ) + + api.HandlerFromMuxWithBaseURL(controller, r, APIV1Prefix) + + url, err := url.Parse(apiConfig.Listen) + if err != nil { + return err + } + + listener, err := net.Listen("tcp", url.Host) + if err != nil { + return err + } + log.Infof("Start listen api %s", listener.Addr()) + go func() { + err := http.Serve(listener, r) + if err != nil && !errors.Is(err, http.ErrServerClosed) { + log.Errorf("listen address fail %s", err) + } + }() + + lc.Append(fx.Hook{ + OnStop: func(ctx context.Context) error { + return listener.Close() + }, + }) + return nil +} diff --git a/api/custom_responser.go b/api/custom_response.go similarity index 100% rename from api/custom_responser.go rename to api/custom_response.go diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 3b94daa9..bf5b8058 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -20,6 +20,10 @@ import ( "github.com/go-chi/chi/v5" ) +const ( + Jwt_tokenScopes = "jwt_token.Scopes" +) + // VersionResult defines model for VersionResult. type VersionResult struct { // ApiVersion runtime version @@ -279,6 +283,8 @@ type MiddlewareFunc func(http.Handler) http.Handler func (siw *ServerInterfaceWrapper) GetVersion(w http.ResponseWriter, r *http.Request) { ctx := r.Context() + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetVersion(&JiaozifsResponse{w}, r) })) @@ -411,13 +417,15 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl } // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/3yST2/UMBDFv0o0cAxxutx8q1CBlRCqaNVLVSHjTBOX+A/jSVdVle+OxtnNLoLl5H+/", - "57HnvVew0acYMHAG/QrZDuhNmd4hZRfDN8zTyLKRKCYkdliOTXLfnxdElh1mSy5xWQJNgZ3H6gDUwC8J", - "QUNmcqGHuYaz2kSxJ+PPa+caCH9NjrADfQ9H7vRJD6ss/nhCyzCLzoXH+HfF0fzEjzfV59vb6+ryegs1", - "jM5iyChoMF5uuUzGDlhtmhZqmGgEDQNzylqp3W7XmHLcROrVXpvVl+2Hq683V+82TdsM7Ef5NTse8Vhy", - "qba2Ai6atmmFiwmDSQ40vC9bNSTDQ2m8Oulcj8UZ8cXIZ7YdaPiEfLf2hDCnKM8RbtO2MtgYGAMvLqbR", - "2aJVT3m5dMmAzN4SPoKGN+oYErVPiPozHqW7//dRiDx5b+hFEoI8UagOkAld9Y/QmD6LxTZ6L56WKhlJ", - "CND3Z3xcgApDl6ILvPqlTHLq+QLmh/l3AAAA//+mze959wIAAA==", + "H4sIAAAAAAAC/3xTwW7bMAz9FYPb0bPd7uZbMXRbt2Eo1qI7BEHAyEzM1JY0iW6QBf73QXLqOOiSUyzq", + "PfJReW8PyrTWaNLiodyDVzW1GD+fyHk2+hf5rpFQsM5YcsIUr9Hy4mWAhGNFXjm2Eo/gOi3cUvIKSEF2", + "lqAEL471GvoUznKtM2uH7Xlun4KjPx07qqCcwRE3lTQfaWa5ISWR5kl1jmX3ELYc1liiZ7XATupx/UCK", + "5ePoWsQG0cqYZ6YRzkHvUIMUNEYqayGnsYmohSd/ugVa/k670GyzlYWYZ4pvsCR05D4b16JACd9+P0I6", + "kRNv3+oxXKnLakbEJSUe2+ZymxFxvk14YNYr8/Yf3TCav7zyydfHx/vk5v4OUmhYkfYUwIcRNxZVTcl1", + "VkAKnWsOa/oyz7fbbYbxOjNunR+4Pv9x9+n258Pth+usyGppm7CLsDQ0HTrMG+0GV1mRFfHxLGm0DCV8", + "jKUULEodXZFP3Lmm6P7gfQwL3VVQwheSp9F3jrw1QVDAXRdF+FFGC2kZkmIbVpGbb/zQdMhZ+HrvaAUl", + "vMuPQcwPKcxPIxhf+HJWpiaHcrafemw27+cp+K5t0e1CRkk6p5PXFqir5D+xxbUPIVOmbUOq+osTABER", + "5n16mquhHi6CAHKhfeSesckASUhX1rCW0Q45Ws5frqCf9/8CAAD//zGNbEq4BAAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 53766353..a9acb731 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -1,8 +1,8 @@ openapi: "3.0.0" info: - description: lakeFS HTTP API - title: lakeFS API + description: jiaozifs HTTP API + title: jiaozifs API license: name: "Apache 2.0" url: https://www.apache.org/licenses/LICENSE-2.0.html @@ -10,8 +10,32 @@ info: servers: - url: "/api/v1" - description: lakeFS server endpoint + description: jiaozifs server endpoint + +security: + - jwt_token: ["aaaa"] + - basic_auth: ["aaaaa"] components: + securitySchemes: + basic_auth: + type: http + scheme: basic + jwt_token: + type: http + scheme: bearer + bearerFormat: JWT + cookie_auth: + type: apiKey + in: cookie + name: internal_auth_session + oidc_auth: + type: apiKey + in: cookie + name: oidc_auth_session + saml_auth: + type: apiKey + in: cookie + name: saml_auth_session schemas: VersionResult: type: object @@ -40,6 +64,8 @@ paths: - common operationId: getVersion summary: return program and runtime version + security: + - jwt_token: [] responses: 200: description: program version diff --git a/auth/basic_auth.go b/auth/basic_auth.go new file mode 100644 index 00000000..8832b06d --- /dev/null +++ b/auth/basic_auth.go @@ -0,0 +1 @@ +package auth diff --git a/cmd/daemon.go b/cmd/daemon.go index 6e85c991..e6db8d3b 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -5,20 +5,16 @@ package cmd import ( "context" - "errors" - "github.com/go-chi/chi/v5" logging "github.com/ipfs/go-log/v2" - "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/api/api_impl" "github.com/jiaozifs/jiaozifs/config" "github.com/jiaozifs/jiaozifs/fx_opt" + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/models/migrations" "github.com/jiaozifs/jiaozifs/utils" - middleware "github.com/oapi-codegen/nethttp-middleware" "github.com/spf13/cobra" - "go.uber.org/fx" - "net" - "net/http" - "net/url" + "github.com/spf13/viper" + "github.com/uptrace/bun" ) var log = logging.Logger("main") @@ -46,9 +42,13 @@ var daemonCmd = &cobra.Command{ //config fx_opt.Override(new(*config.Config), cfg), fx_opt.Override(new(*config.APIConfig), &cfg.API), - //business + fx_opt.Override(new(*config.DatabaseConfig), &cfg.Database), + //database + fx_opt.Override(new(*bun.DB), models.SetupDatabase), + fx_opt.Override(fx_opt.NextInvoke(), migrations.MigrateDatabase), + fx_opt.Override(new(*models.IUserRepo), models.NewUserRepo), //api - fx_opt.Override(fx_opt.NextInvoke(), setupAPI), + fx_opt.Override(fx_opt.NextInvoke(), api_impl.SetupAPI), ) if err != nil { return err @@ -65,47 +65,9 @@ var daemonCmd = &cobra.Command{ func init() { rootCmd.AddCommand(daemonCmd) -} - -func setupAPI(lc fx.Lifecycle, apiConfig *config.APIConfig, controller api_impl.APIController) error { - swagger, err := api.GetSwagger() - if err != nil { - return err - } - - // Clear out the servers array in the swagger spec, that skips validating - // that server names match. We don't know how this thing will be run. - swagger.Servers = nil - // This is how you set up a basic chi router - r := chi.NewRouter() - - // Use our validation middleware to check all requests against the - // OpenAPI schema. - r.Use(middleware.OapiRequestValidator(swagger)) - - api.HandlerFromMux(controller, r) - - url, err := url.Parse(apiConfig.Listen) - if err != nil { - return err - } - - listener, err := net.Listen("tcp", url.Host) - if err != nil { - return err - } - log.Infof("Start listen api %s", listener.Addr()) - go func() { - err := http.Serve(listener, r) - if err != nil && !errors.Is(err, http.ErrServerClosed) { - log.Errorf("listen address fail %s", err) - } - }() + daemonCmd.Flags().String("db", "", "pg connection string eg. postgres://user:pass@localhost:5432/jiaozifs?sslmode=disable") + daemonCmd.Flags().String("log-level", "INFO", "set log level eg. DEBUG INFO ERROR") - lc.Append(fx.Hook{ - OnStop: func(ctx context.Context) error { - return listener.Close() - }, - }) - return nil + viper.BindPFlag("database.connection", daemonCmd.Flags().Lookup("db")) + viper.BindPFlag("log.level", daemonCmd.Flags().Lookup("log-level")) } diff --git a/cmd/init.go b/cmd/init.go index dd30f49a..7a1b3de3 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -6,6 +6,7 @@ package cmd import ( "github.com/jiaozifs/jiaozifs/config" "github.com/spf13/cobra" + "github.com/spf13/viper" ) // initCmd represents the init command @@ -13,6 +14,10 @@ var initCmd = &cobra.Command{ Use: "init", Short: "init jiaozifs ", Long: `create default config file for jiaoozifs`, + PreRun: func(cmd *cobra.Command, args []string) { + //provent duplicate bind flag with daemon + viper.BindPFlag("database.connection", cmd.Flags().Lookup("db")) + }, RunE: func(cmd *cobra.Command, args []string) error { return config.InitConfig() }, @@ -20,4 +25,5 @@ var initCmd = &cobra.Command{ func init() { rootCmd.AddCommand(initCmd) + initCmd.Flags().String("db", "", "pg connection string eg. postgres://user:pass@localhost:5432/jiaozifs?sslmode=disable") } diff --git a/cmd/root.go b/cmd/root.go index 57cb0563..17194f17 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -5,6 +5,7 @@ package cmd import ( "github.com/spf13/cobra" + "github.com/spf13/viper" "os" ) @@ -15,9 +16,6 @@ var rootCmd = &cobra.Command{ Use: "jiaozifs", Short: "version file for manage datasets", Long: ``, - // Uncomment the following line if your bare application - // has an action associated with it: - // Run: func(cmd *cobra.Command, args []string) { }, } // Execute adds all child commands to the root command and sets flags appropriately. @@ -31,5 +29,5 @@ func Execute() { func init() { rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.jiaozifs/config.yaml)") - rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + viper.BindPFlag("config", rootCmd.Flags().Lookup("config")) } diff --git a/cmd/version.go b/cmd/version.go index 3d6df0e0..84c87385 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -6,6 +6,7 @@ package cmd import ( "fmt" "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/api/api_impl" "github.com/jiaozifs/jiaozifs/config" "github.com/jiaozifs/jiaozifs/version" "github.com/spf13/cobra" @@ -17,16 +18,19 @@ var versionCmd = &cobra.Command{ Short: "version of jiaozifs", Long: `jiaozifs version`, RunE: func(cmd *cobra.Command, args []string) error { - cfg, err := config.LoadConfig(cfgFile) + swagger, err := api.GetSwagger() if err != nil { return err } - client, err := api.NewClient(cfg.API.Listen) + fmt.Println("Version ", version.UserVersion()) + fmt.Println("API Version ", swagger.Info.Version) + + //get runtime version + cfg, err := config.LoadConfig(cfgFile) if err != nil { return err } - - swagger, err := api.GetSwagger() + client, err := api.NewClient(cfg.API.Listen + api_impl.APIV1Prefix) if err != nil { return err } @@ -42,8 +46,6 @@ var versionCmd = &cobra.Command{ if okResp.JSON200 == nil { return fmt.Errorf("request version fail %d %s", okResp.HTTPResponse.StatusCode, okResp.HTTPResponse.Body) } - fmt.Println("Version ", version.UserVersion()) - fmt.Println("API Version ", swagger.Info.Version) fmt.Println("Runtime Version ", okResp.JSON200.Version) fmt.Println("Runtime API Version ", okResp.JSON200.ApiVersion) return nil @@ -52,14 +54,4 @@ var versionCmd = &cobra.Command{ func init() { rootCmd.AddCommand(versionCmd) - - // Here you will define your flags and configuration settings. - - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // versionCmd.PersistentFlags().String("foo", "", "A help for foo") - - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // versionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } diff --git a/config/config.go b/config/config.go index fc7adf04..4a1d9051 100644 --- a/config/config.go +++ b/config/config.go @@ -13,9 +13,10 @@ import ( var log = logging.Logger("log") type Config struct { - Path string `mapstructure:"config"` - Log LogConfig `mapstructure:"log"` - API APIConfig `mapstructure:"api"` + Path string `mapstructure:"config"` + Log LogConfig `mapstructure:"log"` + API APIConfig `mapstructure:"api"` + Database DatabaseConfig `mapstructure:"database"` } type LogConfig struct { @@ -26,13 +27,16 @@ type APIConfig struct { Listen string `mapstructure:"listen"` } +type DatabaseConfig struct { + Connection string `mapstructure:"connection"` +} + func InitConfig() error { // Find home directory. home, err := os.UserHomeDir() cobra.CheckErr(err) jiaoziHome := path.Join(home, ".jiaozifs") defaultPath := path.Join(jiaoziHome, "config.toml") - // Search config in home directory with name ".jiaozifs" (without extension). viper.AddConfigPath(path.Join(home, ".jiaozifs")) viper.SetConfigType("toml") diff --git a/go.mod b/go.mod index 4d754de4..15a38528 100644 --- a/go.mod +++ b/go.mod @@ -6,23 +6,29 @@ require ( github.com/deepmap/oapi-codegen/v2 v2.0.1-0.20231120160225-add3126ee845 github.com/getkin/kin-openapi v0.118.0 github.com/go-chi/chi/v5 v5.0.10 + github.com/google/uuid v1.4.0 github.com/ipfs/go-log/v2 v2.5.1 github.com/mitchellh/mapstructure v1.5.0 github.com/oapi-codegen/nethttp-middleware v1.0.1 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.17.0 + github.com/uptrace/bun v1.1.16 + github.com/uptrace/bun/dialect/pgdialect v1.1.16 + github.com/uptrace/bun/driver/pgdriver v1.1.16 go.uber.org/fx v1.20.1 gopkg.in/yaml.v2 v2.4.0 ) require ( github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-chi/cors v1.2.1 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/swag v0.22.4 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/yaml v0.2.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -37,10 +43,14 @@ require ( github.com/spf13/cast v1.5.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/dig v1.17.0 // indirect go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.23.0 // indirect + golang.org/x/crypto v0.13.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/sys v0.12.0 // indirect @@ -48,4 +58,5 @@ require ( golang.org/x/tools v0.13.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + mellium.im/sasl v0.3.1 // indirect ) diff --git a/go.sum b/go.sum index 63fc335f..3014f39d 100644 --- a/go.sum +++ b/go.sum @@ -68,6 +68,8 @@ github.com/getkin/kin-openapi v0.118.0 h1:z43njxPmJ7TaPpMSCQb7PN0dEYno4tyBPQcrFd github.com/getkin/kin-openapi v0.118.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= +github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -132,6 +134,8 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= @@ -150,6 +154,8 @@ github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -214,6 +220,7 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -222,10 +229,22 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/uptrace/bun v1.1.16 h1:cn9cgEMFwcyYRsQLfxCRMUxyK1WaHwOVrR3TvzEFZ/A= +github.com/uptrace/bun v1.1.16/go.mod h1:7HnsMRRvpLFUcquJxp22JO8PsWKpFQO/gNXqqsuGWg8= +github.com/uptrace/bun/dialect/pgdialect v1.1.16 h1:eUPZ+YCJ69BA+W1X1ZmpOJSkv1oYtinr0zCXf7zCo5g= +github.com/uptrace/bun/dialect/pgdialect v1.1.16/go.mod h1:KQjfx/r6JM0OXfbv0rFrxAbdkPD7idK8VitnjIV9fZI= +github.com/uptrace/bun/driver/pgdriver v1.1.16 h1:b/NiSXk6Ldw7KLfMLbOqIkm4odHd7QiNOCPLqPFJjK4= +github.com/uptrace/bun/driver/pgdriver v1.1.16/go.mod h1:Rmfbc+7lx1z/umjMyAxkOHK81LgnGj71XC5YpA6k1vU= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -246,6 +265,7 @@ go.uber.org/fx v1.20.1 h1:zVwVQGS8zYvhh9Xxcu4w1M6ESyeMzebzj2NbSayZ4Mk= go.uber.org/fx v1.20.1/go.mod h1:iSYNbHf2y55acNCwCXKx7LbWb5WG1Bnue5RDXz1OREg= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= @@ -259,6 +279,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -572,6 +594,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo= +mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/models/migrations/20210505110026_init_project.go b/models/migrations/20210505110026_init_project.go new file mode 100644 index 00000000..9aed711c --- /dev/null +++ b/models/migrations/20210505110026_init_project.go @@ -0,0 +1,30 @@ +package migrations + +import ( + "context" + "github.com/jiaozifs/jiaozifs/models" + "github.com/uptrace/bun" +) + +func init() { + Migrations.MustRegister(func(ctx context.Context, db *bun.DB) error { + _, err := db.Exec(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp";`) + if err != nil { + return err + } + + _, err = db.NewCreateTable(). + Model((*models.User)(nil)). + Exec(ctx) + if err != nil { + return err + } + + _, err = db.NewCreateIndex(). + Model((*models.User)(nil)). + Index("name_idx"). + Column("name"). + Exec(ctx) + return err + }, nil) +} diff --git a/models/migrations/main.go b/models/migrations/main.go new file mode 100644 index 00000000..c3e26978 --- /dev/null +++ b/models/migrations/main.go @@ -0,0 +1,26 @@ +package migrations + +import ( + "context" + "github.com/uptrace/bun" + "github.com/uptrace/bun/migrate" +) + +var Migrations = migrate.NewMigrations() + +func init() { + if err := Migrations.DiscoverCaller(); err != nil { + panic(err) + } +} + +func MigrateDatabase(ctx context.Context, sqlDB *bun.DB) error { + migrator := migrate.NewMigrator(sqlDB, Migrations) + err := migrator.Init(ctx) + if err != nil { + return err + } + + _, err = migrator.Migrate(ctx) + return err +} diff --git a/models/models.go b/models/models.go new file mode 100644 index 00000000..422dfe4f --- /dev/null +++ b/models/models.go @@ -0,0 +1,28 @@ +package models + +import ( + "context" + "database/sql" + "github.com/jiaozifs/jiaozifs/config" + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect/pgdialect" + "github.com/uptrace/bun/driver/pgdriver" + "go.uber.org/fx" +) + +func SetupDatabase(ctx context.Context, lc fx.Lifecycle, dbConfig *config.DatabaseConfig) (*bun.DB, error) { + sqlDB := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dbConfig.Connection))) + _, err := sqlDB.Conn(ctx) + if err != nil { + return nil, err + } + + bunDB := bun.NewDB(sqlDB, pgdialect.New(), bun.WithDiscardUnknownColumns()) + lc.Append(fx.Hook{ + OnStop: func(ctx context.Context) error { + return bunDB.Close() + }, + }) + + return bunDB, nil +} diff --git a/models/user.go b/models/user.go new file mode 100644 index 00000000..4ceb9327 --- /dev/null +++ b/models/user.go @@ -0,0 +1,52 @@ +package models + +import ( + "context" + "github.com/google/uuid" + "github.com/uptrace/bun" + "time" +) + +var _user = (*User)(nil) + +type User struct { + bun.BaseModel `bun:"table:users,alias:u"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` + Name string `bun:"name,notnull"` + Email string `bun:"email,notnull"` + EncryptedPassword string `bun:"encrypted_password"` + CurrentSignInAt int64 `bun:"current_sign_in_at"` + LastSignInAt int64 `bun:"last_sign_in_at"` + CurrentSignInIP string `bun:"current_sign_in_ip"` + LastSignInIP string `bun:"last_sign_in_ip"` + CreatedAt time.Time `bun:"created_at,type:timestamp"` + UpdatedAt time.Time `bun:"updated_at,type:timestamp"` +} + +type IUserRepo interface { + GetUser(ctx context.Context, id uuid.UUID) (*User, error) + Insert(ctx context.Context, user *User) (*User, error) +} + +var _ IUserRepo = (*UserRepo)(nil) + +type UserRepo struct { + *bun.DB +} + +func NewUserRepo(db *bun.DB) IUserRepo { + return &UserRepo{db} +} + +func (userRepo *UserRepo) GetUser(ctx context.Context, id uuid.UUID) (*User, error) { + user := &User{} + return user, userRepo.DB.NewSelect().Model(_user).Where("id = 1").Scan(ctx, &user) +} + +func (userRepo *UserRepo) Insert(ctx context.Context, user *User) (*User, error) { + _, err := userRepo.DB.NewInsert().Model(user).Exec(ctx) + if err != nil { + return nil, err + } + return user, nil +} From 6e9dafdde533a47ff6b9a36129ee7d9c45271a56 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Tue, 28 Nov 2023 18:31:07 +0800 Subject: [PATCH 015/210] feat: add simple quick start --- README.md | 16 ++++++++++++++++ test.txt | 2 -- 2 files changed, 16 insertions(+), 2 deletions(-) delete mode 100644 test.txt diff --git a/README.md b/README.md index ca368e74..a101926f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,18 @@ # jiaozifs version control file system. + +## quick start + +build +```bash +git clone https://github.com/jiaozifs/jiaozifs.git +make build +``` + +init and running +```bash +./jiaozi init --db postgres://li:li123@localhost:5432/jiaozifs?sslmode=disable + +./jiaozi daemon +``` + diff --git a/test.txt b/test.txt deleted file mode 100644 index d969f6c5..00000000 --- a/test.txt +++ /dev/null @@ -1,2 +0,0 @@ -aa -aa From 10115135a54b8a0460780a3cc87dab3ea03af42f Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Wed, 29 Nov 2023 10:45:04 +0800 Subject: [PATCH 016/210] chore: rename executable file name --- .gitignore | 1 + README.md | 4 ++-- api/tmpls/chi/chi-handler.tmpl | 2 +- api/tmpls/chi/chi-interface.tmpl | 2 +- api/tmpls/chi/chi-middleware.tmpl | 2 +- makefile | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 8fc6f5ff..73da75d0 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ *.so *.dylib jiaozifs +jzfs # goland *.idea diff --git a/README.md b/README.md index a101926f..44442040 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ make build init and running ```bash -./jiaozi init --db postgres://li:li123@localhost:5432/jiaozifs?sslmode=disable +./jzfs init --db postgres://li:li123@localhost:5432/jiaozifs?sslmode=disable -./jiaozi daemon +./jzfs daemon ``` diff --git a/api/tmpls/chi/chi-handler.tmpl b/api/tmpls/chi/chi-handler.tmpl index 066e28ac..113e4d7a 100644 --- a/api/tmpls/chi/chi-handler.tmpl +++ b/api/tmpls/chi/chi-handler.tmpl @@ -47,4 +47,4 @@ r.{{.Method | lower | title }}(options.BaseURL+"{{.Path | swaggerUriToChiUri}}", }) {{end}} return r -} \ No newline at end of file +} diff --git a/api/tmpls/chi/chi-interface.tmpl b/api/tmpls/chi/chi-interface.tmpl index a561c253..72e8cf76 100644 --- a/api/tmpls/chi/chi-interface.tmpl +++ b/api/tmpls/chi/chi-interface.tmpl @@ -14,4 +14,4 @@ type Unimplemented struct {} func (_ Unimplemented) {{.OperationId}}(w *JiaozifsResponse, r *http.Request{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) { w.WriteHeader(http.StatusNotImplemented) } - {{end}} \ No newline at end of file + {{end}} diff --git a/api/tmpls/chi/chi-middleware.tmpl b/api/tmpls/chi/chi-middleware.tmpl index 78fa82ef..fc9269df 100644 --- a/api/tmpls/chi/chi-middleware.tmpl +++ b/api/tmpls/chi/chi-middleware.tmpl @@ -258,4 +258,4 @@ type TooManyValuesForParamError struct { func (e *TooManyValuesForParamError) Error() string { return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) -} \ No newline at end of file +} diff --git a/makefile b/makefile index 5dab8bbd..4920ee18 100644 --- a/makefile +++ b/makefile @@ -24,4 +24,4 @@ swagger-srv: swagger serve $(SWAGGER_ARG) -F swagger ./api/swagger.yml build:gen-api - go build $(GOFLAGS) -o jiaozifs + go build $(GOFLAGS) -o jzfs From 3615682f5b39ab56ab4a474fad15551ee3c43d50 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Wed, 29 Nov 2023 13:46:17 +0800 Subject: [PATCH 017/210] feat: add golangci lint --- .github/workflows/basic_check.yml | 43 +++++++++++++++++ .github/workflows/test.yml | 46 +++++++++++++++++++ .gitignore | 4 +- .golangci.yml | 29 ++++++++++++ api/api_impl/common.go | 7 +-- api/api_impl/server.go | 11 +++-- api/api_impl/utils.go | 22 --------- api/custom_response.go | 2 +- api/docs.go | 2 +- api/jiaozifs.gen.go | 4 +- cmd/daemon.go | 13 ++---- cmd/init.go | 9 ++-- cmd/root.go | 8 ++-- cmd/version.go | 8 ++-- config/config.go | 9 ++-- .../migrations/20210505110026_init_project.go | 1 + models/migrations/main.go | 1 + models/models.go | 1 + models/user.go | 6 ++- 19 files changed, 161 insertions(+), 65 deletions(-) create mode 100644 .github/workflows/basic_check.yml create mode 100644 .github/workflows/test.yml create mode 100644 .golangci.yml delete mode 100644 api/api_impl/utils.go diff --git a/.github/workflows/basic_check.yml b/.github/workflows/basic_check.yml new file mode 100644 index 00000000..3ba17379 --- /dev/null +++ b/.github/workflows/basic_check.yml @@ -0,0 +1,43 @@ +name: basic-check + +on: + push: + branches: + - master + pull_request: + branches: + - '**' + +jobs: + check: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.20.9' + cache: true + + - name: install deps + run: | + sudo apt-get update + sudo apt-get -o Acquire::Retries=3 install make gcc git curl wget -y + + - name: Build + env: + GOPROXY: "https://proxy.golang.org,direct" + GO111MODULE: "on" + run: | + make build + + - name: Lint + run: | + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -d -b $(go env GOPATH)/bin v1.55.1 + golangci-lint run --timeout 10m + + - name: Detect changes + run: | + git status --porcelain + test -z "$(git status --porcelain)" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..03f2143a --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,46 @@ +name: basic-check + +on: + push: + branches: + - master + pull_request: + branches: + - '**' + +jobs: + test: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.20.9' + cache: true + + - name: install deps + run: | + sudo apt-get update + sudo apt-get -o Acquire::Retries=3 install make gcc git curl wget -y + + - name: Build + env: + GOPROXY: "https://proxy.golang.org,direct" + GO111MODULE: "on" + run: | + make build + + - name: Test + run: | + go test -coverpkg=./... -coverprofile=coverage.txt -covermode=atomic -timeout=30m -parallel=4 -v ./... + + - name: Upload + uses: codecov/codecov-action@v3 + with: + token: + files: ./coverage.txt + name: jzfs + fail_ci_if_error: true + verbose: true diff --git a/.gitignore b/.gitignore index 73da75d0..8dbb396e 100644 --- a/.gitignore +++ b/.gitignore @@ -16,8 +16,8 @@ jzfs # Test binary, built with `go test -c` *.test -# Output of the go coverage tool, specifically when used with LiteIDE -*.out +# Test coverage file +coverage.txt # Dependency directories (remove the comment below to include it) # vendor/ diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000..5ba01f79 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,29 @@ +linters: + disable-all: true + enable: + - gofmt + - govet + - misspell + - goconst + - revive + - errcheck + - unconvert + - staticcheck + - unused + - stylecheck + - gosimple + - goimports +issues: + exclude: + - "should have( a package)? comment" + + exclude-rules: + exclude-use-default: false + +linters-settings: + goconst: + min-occurrences: 6 + +run: + skip-dirs: + skip-files: diff --git a/api/api_impl/common.go b/api/api_impl/common.go index 4f409c82..dd71cde3 100644 --- a/api/api_impl/common.go +++ b/api/api_impl/common.go @@ -1,10 +1,11 @@ -package api_impl +package apiimpl import ( + "net/http" + "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/version" "go.uber.org/fx" - "net/http" ) var _ api.ServerInterface = (*APIController)(nil) @@ -13,7 +14,7 @@ type APIController struct { fx.In } -func (A APIController) GetVersion(w *api.JiaozifsResponse, r *http.Request) { +func (A APIController) GetVersion(w *api.JiaozifsResponse, _ *http.Request) { swagger, err := api.GetSwagger() if err != nil { w.RespError(err) diff --git a/api/api_impl/server.go b/api/api_impl/server.go index 9f3c73c3..480e42d8 100644 --- a/api/api_impl/server.go +++ b/api/api_impl/server.go @@ -1,8 +1,14 @@ -package api_impl +package apiimpl import ( "context" "errors" + + "net" + "net/http" + + "net/url" + "github.com/getkin/kin-openapi/openapi3filter" "github.com/go-chi/chi/v5" "github.com/go-chi/cors" @@ -11,9 +17,6 @@ import ( "github.com/jiaozifs/jiaozifs/config" middleware "github.com/oapi-codegen/nethttp-middleware" "go.uber.org/fx" - "net" - "net/http" - "net/url" ) var log = logging.Logger("rpc") diff --git a/api/api_impl/utils.go b/api/api_impl/utils.go deleted file mode 100644 index a8d38568..00000000 --- a/api/api_impl/utils.go +++ /dev/null @@ -1,22 +0,0 @@ -package api_impl - -import ( - "encoding/json" - "net/http" -) - -func writeJson(w http.ResponseWriter, v interface{}) { - data, err := json.Marshal(v) - if err != nil { - writeError(w, err) - return - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - _, _ = w.Write(data) -} - -func writeError(w http.ResponseWriter, err error) { - w.WriteHeader(http.StatusOK) - w.Write([]byte(err.Error())) -} diff --git a/api/custom_response.go b/api/custom_response.go index ede45c79..0823c893 100644 --- a/api/custom_response.go +++ b/api/custom_response.go @@ -22,5 +22,5 @@ func (response *JiaozifsResponse) RespJSON(v interface{}) { func (response *JiaozifsResponse) RespError(err error) { response.WriteHeader(http.StatusOK) - response.Write([]byte(err.Error())) + _, _ = response.Write([]byte(err.Error())) } diff --git a/api/docs.go b/api/docs.go index 290454b3..7e118509 100644 --- a/api/docs.go +++ b/api/docs.go @@ -1,4 +1,4 @@ -// Package apigen provides generated code for our OpenAPI +// Package api provides generated code for our OpenAPI package api //go:generate go run github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen -package api -templates ./tmpls -generate "types,client,chi-server,spec" -o jiaozifs.gen.go ./swagger.yml diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index bf5b8058..89dbb807 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -414,7 +414,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl }) return r -} // Base64 encoded, gzipped, json marshaled Swagger object +} + +// Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ "H4sIAAAAAAAC/3xTwW7bMAz9FYPb0bPd7uZbMXRbt2Eo1qI7BEHAyEzM1JY0iW6QBf73QXLqOOiSUyzq", diff --git a/cmd/daemon.go b/cmd/daemon.go index e6db8d3b..556e1945 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -1,12 +1,10 @@ -/* -Copyright © 2023 NAME HERE -*/ package cmd import ( "context" + logging "github.com/ipfs/go-log/v2" - "github.com/jiaozifs/jiaozifs/api/api_impl" + apiImpl "github.com/jiaozifs/jiaozifs/api/api_impl" "github.com/jiaozifs/jiaozifs/config" "github.com/jiaozifs/jiaozifs/fx_opt" "github.com/jiaozifs/jiaozifs/models" @@ -48,7 +46,7 @@ var daemonCmd = &cobra.Command{ fx_opt.Override(fx_opt.NextInvoke(), migrations.MigrateDatabase), fx_opt.Override(new(*models.IUserRepo), models.NewUserRepo), //api - fx_opt.Override(fx_opt.NextInvoke(), api_impl.SetupAPI), + fx_opt.Override(fx_opt.NextInvoke(), apiImpl.SetupAPI), ) if err != nil { return err @@ -59,7 +57,6 @@ var daemonCmd = &cobra.Command{ <-shutdown log.Info("graceful shutdown") return stop(cmd.Context()) - return nil }, } @@ -68,6 +65,6 @@ func init() { daemonCmd.Flags().String("db", "", "pg connection string eg. postgres://user:pass@localhost:5432/jiaozifs?sslmode=disable") daemonCmd.Flags().String("log-level", "INFO", "set log level eg. DEBUG INFO ERROR") - viper.BindPFlag("database.connection", daemonCmd.Flags().Lookup("db")) - viper.BindPFlag("log.level", daemonCmd.Flags().Lookup("log-level")) + _ = viper.BindPFlag("database.connection", daemonCmd.Flags().Lookup("db")) + _ = viper.BindPFlag("log.level", daemonCmd.Flags().Lookup("log-level")) } diff --git a/cmd/init.go b/cmd/init.go index 7a1b3de3..3168af92 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -1,6 +1,3 @@ -/* -Copyright © 2023 NAME HERE -*/ package cmd import ( @@ -14,9 +11,9 @@ var initCmd = &cobra.Command{ Use: "init", Short: "init jiaozifs ", Long: `create default config file for jiaoozifs`, - PreRun: func(cmd *cobra.Command, args []string) { - //provent duplicate bind flag with daemon - viper.BindPFlag("database.connection", cmd.Flags().Lookup("db")) + PreRunE: func(cmd *cobra.Command, args []string) error { + //protect duplicate bind flag with daemon + return viper.BindPFlag("database.connection", cmd.Flags().Lookup("db")) }, RunE: func(cmd *cobra.Command, args []string) error { return config.InitConfig() diff --git a/cmd/root.go b/cmd/root.go index 17194f17..c8b1ed59 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,12 +1,10 @@ -/* -Copyright © 2023 githun.com/jiaozifs/jiaozifs -*/ package cmd import ( + "os" + "github.com/spf13/cobra" "github.com/spf13/viper" - "os" ) var cfgFile string @@ -29,5 +27,5 @@ func Execute() { func init() { rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.jiaozifs/config.yaml)") - viper.BindPFlag("config", rootCmd.Flags().Lookup("config")) + _ = viper.BindPFlag("config", rootCmd.Flags().Lookup("config")) } diff --git a/cmd/version.go b/cmd/version.go index 84c87385..523361c0 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -1,12 +1,10 @@ -/* -Copyright © 2023 NAME HERE -*/ package cmd import ( "fmt" + "github.com/jiaozifs/jiaozifs/api" - "github.com/jiaozifs/jiaozifs/api/api_impl" + apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" "github.com/jiaozifs/jiaozifs/config" "github.com/jiaozifs/jiaozifs/version" "github.com/spf13/cobra" @@ -30,7 +28,7 @@ var versionCmd = &cobra.Command{ if err != nil { return err } - client, err := api.NewClient(cfg.API.Listen + api_impl.APIV1Prefix) + client, err := api.NewClient(cfg.API.Listen + apiimpl.APIV1Prefix) if err != nil { return err } diff --git a/config/config.go b/config/config.go index 4a1d9051..213ff4f4 100644 --- a/config/config.go +++ b/config/config.go @@ -2,16 +2,15 @@ package config import ( "fmt" - logging "github.com/ipfs/go-log/v2" + + "os" + "path" + ms "github.com/mitchellh/mapstructure" "github.com/spf13/cobra" "github.com/spf13/viper" - "os" - "path" ) -var log = logging.Logger("log") - type Config struct { Path string `mapstructure:"config"` Log LogConfig `mapstructure:"log"` diff --git a/models/migrations/20210505110026_init_project.go b/models/migrations/20210505110026_init_project.go index 9aed711c..e317fe68 100644 --- a/models/migrations/20210505110026_init_project.go +++ b/models/migrations/20210505110026_init_project.go @@ -2,6 +2,7 @@ package migrations import ( "context" + "github.com/jiaozifs/jiaozifs/models" "github.com/uptrace/bun" ) diff --git a/models/migrations/main.go b/models/migrations/main.go index c3e26978..dce0dfde 100644 --- a/models/migrations/main.go +++ b/models/migrations/main.go @@ -2,6 +2,7 @@ package migrations import ( "context" + "github.com/uptrace/bun" "github.com/uptrace/bun/migrate" ) diff --git a/models/models.go b/models/models.go index 422dfe4f..1d623217 100644 --- a/models/models.go +++ b/models/models.go @@ -3,6 +3,7 @@ package models import ( "context" "database/sql" + "github.com/jiaozifs/jiaozifs/config" "github.com/uptrace/bun" "github.com/uptrace/bun/dialect/pgdialect" diff --git a/models/user.go b/models/user.go index 4ceb9327..9b11a8b1 100644 --- a/models/user.go +++ b/models/user.go @@ -2,9 +2,11 @@ package models import ( "context" + + "time" + "github.com/google/uuid" "github.com/uptrace/bun" - "time" ) var _user = (*User)(nil) @@ -40,7 +42,7 @@ func NewUserRepo(db *bun.DB) IUserRepo { func (userRepo *UserRepo) GetUser(ctx context.Context, id uuid.UUID) (*User, error) { user := &User{} - return user, userRepo.DB.NewSelect().Model(_user).Where("id = 1").Scan(ctx, &user) + return user, userRepo.DB.NewSelect().Model(_user).Where("id = :id", id).Scan(ctx, &user) } func (userRepo *UserRepo) Insert(ctx context.Context, user *User) (*User, error) { From 238e75a0df19287133388e9890318919025f2fa1 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Wed, 29 Nov 2023 14:28:07 +0800 Subject: [PATCH 018/210] feat:add line lint --- .fend.yaml | 6 ++++++ .github/workflows/basic_check.yml | 3 +++ .github/workflows/test.yml | 2 +- README.md | 1 - docs/refrences.md | 2 +- 5 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 .fend.yaml diff --git a/.fend.yaml b/.fend.yaml new file mode 100644 index 00000000..1526c31d --- /dev/null +++ b/.fend.yaml @@ -0,0 +1,6 @@ +skip: + dir: + - .idea + - .vscode + file: + - jzfs diff --git a/.github/workflows/basic_check.yml b/.github/workflows/basic_check.yml index 3ba17379..d4907058 100644 --- a/.github/workflows/basic_check.yml +++ b/.github/workflows/basic_check.yml @@ -32,6 +32,9 @@ jobs: run: | make build + - name: end-of-file-check + uses: njgibbon/fend@main + - name: Lint run: | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -d -b $(go env GOPATH)/bin v1.55.1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 03f2143a..3b19b4f5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,4 @@ -name: basic-check +name: test on: push: diff --git a/README.md b/README.md index 44442040..e54919c8 100644 --- a/README.md +++ b/README.md @@ -15,4 +15,3 @@ init and running ./jzfs daemon ``` - diff --git a/docs/refrences.md b/docs/refrences.md index a8ba732a..34b8f006 100644 --- a/docs/refrences.md +++ b/docs/refrences.md @@ -23,4 +23,4 @@ git设计 3. 包文件 压缩上述文件到包里面 -4. 传输协议 smart http/ssh \ No newline at end of file +4. 传输协议 smart http/ssh From d25452b43d300a0411d0adf4c63ea11c7fad1e8a Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Wed, 29 Nov 2023 15:02:50 +0800 Subject: [PATCH 019/210] test: just for test --- README.md | 18 +----------------- codecov.yml | 6 ++++++ 2 files changed, 7 insertions(+), 17 deletions(-) create mode 100644 codecov.yml diff --git a/README.md b/README.md index e54919c8..6b337d78 100644 --- a/README.md +++ b/README.md @@ -1,17 +1 @@ -# jiaozifs -version control file system. - -## quick start - -build -```bash -git clone https://github.com/jiaozifs/jiaozifs.git -make build -``` - -init and running -```bash -./jzfs init --db postgres://li:li123@localhost:5432/jiaozifs?sslmode=disable - -./jzfs daemon -``` +# 仅测试 diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..33dad9e6 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,6 @@ +comment: # this is a top-level key + layout: "diff, files" + behavior: default + require_changes: false # if true: only post the comment if coverage changes + require_base: no # [yes :: must have a base report to post] + require_head: yes # [yes :: must have a head report to post] \ No newline at end of file From edf83c22a419091e22cbbd59418dd6c60d873f09 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Wed, 29 Nov 2023 15:44:22 +0800 Subject: [PATCH 020/210] x --- codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codecov.yml b/codecov.yml index 33dad9e6..fd86c5d7 100644 --- a/codecov.yml +++ b/codecov.yml @@ -3,4 +3,4 @@ comment: # this is a top-level key behavior: default require_changes: false # if true: only post the comment if coverage changes require_base: no # [yes :: must have a base report to post] - require_head: yes # [yes :: must have a head report to post] \ No newline at end of file + require_head: yes # [yes :: must have a head report to post] From 1a3dea8af413f879d2b43d05089d3d577fb41012 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Wed, 29 Nov 2023 15:56:11 +0800 Subject: [PATCH 021/210] x --- codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codecov.yml b/codecov.yml index fd86c5d7..1a1acbd5 100644 --- a/codecov.yml +++ b/codecov.yml @@ -3,4 +3,4 @@ comment: # this is a top-level key behavior: default require_changes: false # if true: only post the comment if coverage changes require_base: no # [yes :: must have a base report to post] - require_head: yes # [yes :: must have a head report to post] + require_head: no # [yes :: must have a head report to post] From c928cd7c354fd3cc7bac889ea8dd9a854b47906d Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:08:56 +0800 Subject: [PATCH 022/210] Revert "test: just for test" --- README.md | 18 +++++++++++++++++- codecov.yml | 6 ------ 2 files changed, 17 insertions(+), 7 deletions(-) delete mode 100644 codecov.yml diff --git a/README.md b/README.md index 6b337d78..e54919c8 100644 --- a/README.md +++ b/README.md @@ -1 +1,17 @@ -# 仅测试 +# jiaozifs +version control file system. + +## quick start + +build +```bash +git clone https://github.com/jiaozifs/jiaozifs.git +make build +``` + +init and running +```bash +./jzfs init --db postgres://li:li123@localhost:5432/jiaozifs?sslmode=disable + +./jzfs daemon +``` diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index 1a1acbd5..00000000 --- a/codecov.yml +++ /dev/null @@ -1,6 +0,0 @@ -comment: # this is a top-level key - layout: "diff, files" - behavior: default - require_changes: false # if true: only post the comment if coverage changes - require_base: no # [yes :: must have a base report to post] - require_head: no # [yes :: must have a head report to post] From b3f6fd3dc1bca994301a7d3558bfbb317c7bb4ce Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Wed, 29 Nov 2023 19:36:01 +0800 Subject: [PATCH 023/210] feat: use embed pg for test, go-cmp to check db time --- config/config.go | 1 + go.mod | 12 ++++++++ go.sum | 22 +++++++++++++ models/models.go | 7 +++++ models/models_test.go | 36 ++++++++++++++++++++++ models/user.go | 10 +++--- models/user_test.go | 72 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 models/models_test.go create mode 100644 models/user_test.go diff --git a/config/config.go b/config/config.go index 213ff4f4..423619e9 100644 --- a/config/config.go +++ b/config/config.go @@ -28,6 +28,7 @@ type APIConfig struct { type DatabaseConfig struct { Connection string `mapstructure:"connection"` + Debug bool `mapstructure:"debug"` } func InitConfig() error { diff --git a/go.mod b/go.mod index 15a38528..6fba1d74 100644 --- a/go.mod +++ b/go.mod @@ -20,32 +20,44 @@ require ( ) require ( + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/fatih/color v1.15.0 // indirect + github.com/fergusstrange/embedded-postgres v1.25.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-chi/cors v1.2.1 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/swag v0.22.4 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/yaml v0.2.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/lib/pq v1.10.9 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/perimeterx/marshmallow v1.1.4 // indirect + github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/sagikazarmark/locafero v0.3.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.10.0 // indirect github.com/spf13/cast v1.5.1 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.5.0 // indirect + github.com/stretchr/testify v1.8.4 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/uptrace/bun/extra/bundebug v1.1.16 // indirect github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/dig v1.17.0 // indirect go.uber.org/multierr v1.9.0 // indirect diff --git a/go.sum b/go.sum index 3014f39d..40690bbe 100644 --- a/go.sum +++ b/go.sum @@ -53,6 +53,7 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deepmap/oapi-codegen/v2 v2.0.1-0.20231120160225-add3126ee845 h1:QlRkcr1t+VcHkdk8WJDhuiI94RqmjSgtflB3Q+H8X2k= github.com/deepmap/oapi-codegen/v2 v2.0.1-0.20231120160225-add3126ee845/go.mod h1:pB9cROTwrn6Gj3Rtmcmp5fwV23znquC9tY1rR6+/R3s= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -61,6 +62,10 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fergusstrange/embedded-postgres v1.25.0 h1:sa+k2Ycrtz40eCRPOzI7Ry7TtkWXXJ+YRsxpKMDhxK0= +github.com/fergusstrange/embedded-postgres v1.25.0/go.mod h1:t/MLs0h9ukYM6FSt99R7InCHs1nW0ordoVCcnzmpTYw= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= @@ -119,6 +124,8 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -169,13 +176,18 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -188,12 +200,15 @@ github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6 github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55FuFkxqLw= github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= +github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= @@ -216,6 +231,7 @@ github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -241,10 +257,14 @@ github.com/uptrace/bun/dialect/pgdialect v1.1.16 h1:eUPZ+YCJ69BA+W1X1ZmpOJSkv1oY github.com/uptrace/bun/dialect/pgdialect v1.1.16/go.mod h1:KQjfx/r6JM0OXfbv0rFrxAbdkPD7idK8VitnjIV9fZI= github.com/uptrace/bun/driver/pgdriver v1.1.16 h1:b/NiSXk6Ldw7KLfMLbOqIkm4odHd7QiNOCPLqPFJjK4= github.com/uptrace/bun/driver/pgdriver v1.1.16/go.mod h1:Rmfbc+7lx1z/umjMyAxkOHK81LgnGj71XC5YpA6k1vU= +github.com/uptrace/bun/extra/bundebug v1.1.16 h1:SgicRQGtnjhrIhlYOxdkOm1Em4s6HykmT3JblHnoTBM= +github.com/uptrace/bun/extra/bundebug v1.1.16/go.mod h1:SkiOkfUirBiO1Htc4s5bQKEq+JSeU1TkBVpMsPz2ePM= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -266,6 +286,7 @@ go.uber.org/fx v1.20.1/go.mod h1:iSYNbHf2y55acNCwCXKx7LbWb5WG1Bnue5RDXz1OREg= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= @@ -411,6 +432,7 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= diff --git a/models/models.go b/models/models.go index 1d623217..e2916363 100644 --- a/models/models.go +++ b/models/models.go @@ -4,6 +4,8 @@ import ( "context" "database/sql" + "github.com/uptrace/bun/extra/bundebug" + "github.com/jiaozifs/jiaozifs/config" "github.com/uptrace/bun" "github.com/uptrace/bun/dialect/pgdialect" @@ -19,6 +21,11 @@ func SetupDatabase(ctx context.Context, lc fx.Lifecycle, dbConfig *config.Databa } bunDB := bun.NewDB(sqlDB, pgdialect.New(), bun.WithDiscardUnknownColumns()) + + if dbConfig.Debug { + bunDB.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true))) + } + lc.Append(fx.Hook{ OnStop: func(ctx context.Context) error { return bunDB.Close() diff --git a/models/models_test.go b/models/models_test.go new file mode 100644 index 00000000..bfc42312 --- /dev/null +++ b/models/models_test.go @@ -0,0 +1,36 @@ +package models_test + +import ( + "context" + "fmt" + "github.com/jiaozifs/jiaozifs/models" + "testing" + + embeddedpostgres "github.com/fergusstrange/embedded-postgres" + + "github.com/phayes/freeport" + "github.com/stretchr/testify/require" + + "github.com/jiaozifs/jiaozifs/config" + + "go.uber.org/fx/fxtest" +) + +var testConnTmpl = "postgres://postgres:postgres@localhost:%d/jiaozifs?sslmode=disable" + +func TestSetupDatabase(t *testing.T) { + ctx := context.Background() + port, err := freeport.GetFreePort() + require.NoError(t, err) + + postgres := embeddedpostgres.NewDatabase(embeddedpostgres.DefaultConfig().Port(uint32(port)).Database("jiaozifs")) + err = postgres.Start() + require.NoError(t, err) + defer postgres.Stop() //nolint + + lc := fxtest.NewLifecycle(t) + db, err := models.SetupDatabase(ctx, lc, &config.DatabaseConfig{Connection: fmt.Sprintf(testConnTmpl, port)}) + require.NoError(t, err) + lc.RequireStop() + require.NoError(t, db.PingContext(ctx)) +} diff --git a/models/user.go b/models/user.go index 9b11a8b1..7bfd09c7 100644 --- a/models/user.go +++ b/models/user.go @@ -17,12 +17,12 @@ type User struct { Name string `bun:"name,notnull"` Email string `bun:"email,notnull"` EncryptedPassword string `bun:"encrypted_password"` - CurrentSignInAt int64 `bun:"current_sign_in_at"` - LastSignInAt int64 `bun:"last_sign_in_at"` + CurrentSignInAt time.Time `bun:"current_sign_in_at"` + LastSignInAt time.Time `bun:"last_sign_in_at"` CurrentSignInIP string `bun:"current_sign_in_ip"` LastSignInIP string `bun:"last_sign_in_ip"` - CreatedAt time.Time `bun:"created_at,type:timestamp"` - UpdatedAt time.Time `bun:"updated_at,type:timestamp"` + CreatedAt time.Time `bun:"created_at"` + UpdatedAt time.Time `bun:"updated_at"` } type IUserRepo interface { @@ -42,7 +42,7 @@ func NewUserRepo(db *bun.DB) IUserRepo { func (userRepo *UserRepo) GetUser(ctx context.Context, id uuid.UUID) (*User, error) { user := &User{} - return user, userRepo.DB.NewSelect().Model(_user).Where("id = :id", id).Scan(ctx, &user) + return user, userRepo.DB.NewSelect().Model(user).Where("id = ?", id).Scan(ctx) } func (userRepo *UserRepo) Insert(ctx context.Context, user *User) (*User, error) { diff --git a/models/user_test.go b/models/user_test.go new file mode 100644 index 00000000..e6230e18 --- /dev/null +++ b/models/user_test.go @@ -0,0 +1,72 @@ +package models_test + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + + "github.com/google/uuid" + + "github.com/uptrace/bun" + + "github.com/jiaozifs/jiaozifs/models/migrations" + + "github.com/jiaozifs/jiaozifs/models" + + "github.com/jiaozifs/jiaozifs/config" + "github.com/phayes/freeport" + "go.uber.org/fx/fxtest" + + "github.com/stretchr/testify/require" + + embeddedpostgres "github.com/fergusstrange/embedded-postgres" + + _ "github.com/jiaozifs/jiaozifs/models/migrations" +) + +var dbTimeCmpOpt = cmp.Comparer(func(x, y time.Time) bool { + return x.Unix() == y.Unix() +}) + +func setup(ctx context.Context, t *testing.T) (*embeddedpostgres.EmbeddedPostgres, *bun.DB) { + port, err := freeport.GetFreePort() + require.NoError(t, err) + postgres := embeddedpostgres.NewDatabase(embeddedpostgres.DefaultConfig().Port(uint32(port)).Database("jiaozifs")) + err = postgres.Start() + require.NoError(t, err) + + db, err := models.SetupDatabase(ctx, fxtest.NewLifecycle(t), &config.DatabaseConfig{Debug: true, Connection: fmt.Sprintf(testConnTmpl, port)}) + require.NoError(t, err) + + migrations.MigrateDatabase(ctx, db) + return postgres, db +} +func TestNewUserRepo(t *testing.T) { + ctx := context.Background() + postgres, db := setup(ctx, t) + defer postgres.Stop() //nolint + + repo := models.NewUserRepo(db) + userModel := &models.User{ + Name: "aaa", + Email: "xx@gmail.com", + EncryptedPassword: "aaaaa", + CurrentSignInAt: time.Now(), + LastSignInAt: time.Now(), + CurrentSignInIP: "127.0.0.1", + LastSignInIP: "127.0.0.1", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + newUser, err := repo.Insert(ctx, userModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newUser.ID) + + user, err := repo.GetUser(ctx, newUser.ID) + require.NoError(t, err) + + require.True(t, cmp.Equal(userModel.UpdatedAt, user.UpdatedAt, dbTimeCmpOpt)) +} From 93f3da0b4f3dad4784935ec463cc410c8497ea8f Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Wed, 29 Nov 2023 20:01:03 +0800 Subject: [PATCH 024/210] chore: make lint happy --- .github/workflows/test.yml | 4 ++-- .gitignore | 3 ++- models/models_test.go | 4 +++- models/user.go | 2 -- models/user_test.go | 5 ++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3b19b4f5..5b1e3133 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,13 +34,13 @@ jobs: - name: Test run: | - go test -coverpkg=./... -coverprofile=coverage.txt -covermode=atomic -timeout=30m -parallel=4 -v ./... + go test -coverpkg=./... -coverprofile=coverage.out -covermode=atomic -timeout=30m -parallel=4 -v ./... - name: Upload uses: codecov/codecov-action@v3 with: token: - files: ./coverage.txt + files: ./coverage.out name: jzfs fail_ci_if_error: true verbose: true diff --git a/.gitignore b/.gitignore index 8dbb396e..c1de8634 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,8 @@ jzfs *.test # Test coverage file -coverage.txt +coverage.out + # Dependency directories (remove the comment below to include it) # vendor/ diff --git a/models/models_test.go b/models/models_test.go index bfc42312..c8be9490 100644 --- a/models/models_test.go +++ b/models/models_test.go @@ -3,9 +3,11 @@ package models_test import ( "context" "fmt" - "github.com/jiaozifs/jiaozifs/models" + "testing" + "github.com/jiaozifs/jiaozifs/models" + embeddedpostgres "github.com/fergusstrange/embedded-postgres" "github.com/phayes/freeport" diff --git a/models/user.go b/models/user.go index 7bfd09c7..aa8a6123 100644 --- a/models/user.go +++ b/models/user.go @@ -9,8 +9,6 @@ import ( "github.com/uptrace/bun" ) -var _user = (*User)(nil) - type User struct { bun.BaseModel `bun:"table:users,alias:u"` ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` diff --git a/models/user_test.go b/models/user_test.go index e6230e18..2fb49a46 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -23,8 +23,6 @@ import ( "github.com/stretchr/testify/require" embeddedpostgres "github.com/fergusstrange/embedded-postgres" - - _ "github.com/jiaozifs/jiaozifs/models/migrations" ) var dbTimeCmpOpt = cmp.Comparer(func(x, y time.Time) bool { @@ -41,7 +39,8 @@ func setup(ctx context.Context, t *testing.T) (*embeddedpostgres.EmbeddedPostgre db, err := models.SetupDatabase(ctx, fxtest.NewLifecycle(t), &config.DatabaseConfig{Debug: true, Connection: fmt.Sprintf(testConnTmpl, port)}) require.NoError(t, err) - migrations.MigrateDatabase(ctx, db) + err = migrations.MigrateDatabase(ctx, db) + require.NoError(t, err) return postgres, db } func TestNewUserRepo(t *testing.T) { From 3db46540c8eea3985e67d404beea37621414a556 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Thu, 30 Nov 2023 14:10:50 +0800 Subject: [PATCH 025/210] feat: add block implementation --- .fend.yaml | 3 + .github/workflows/test.yml | 5 + block/adapter.go | 154 ++++ block/azure/adapter.go | 593 ++++++++++++++++ block/azure/adapter_test.go | 84 +++ block/azure/chunkwriting.go | 335 +++++++++ block/azure/client_cache.go | 144 ++++ block/azure/client_test.go | 57 ++ block/azure/main_test.go | 115 +++ block/azure/multipart_block_writer.go | 225 ++++++ block/azure/stats.go | 31 + block/azure/walker.go | 257 +++++++ block/blocktest/adapter.go | 415 +++++++++++ block/errors.go | 13 + block/factory/build.go | 140 ++++ block/gs/adapter.go | 584 ++++++++++++++++ block/gs/adapter_test.go | 78 +++ block/gs/compose.go | 33 + block/gs/compose_test.go | 51 ++ block/gs/main_test.go | 85 +++ block/gs/stats.go | 31 + block/gs/walker.go | 81 +++ block/hashing_reader.go | 57 ++ block/local/adapter.go | 583 ++++++++++++++++ block/local/adapter_test.go | 66 ++ block/local/etag_test.go | 27 + block/local/walker.go | 191 +++++ block/mem/adapter.go | 355 ++++++++++ block/mem/adapter_test.go | 7 + block/namespace.go | 197 ++++++ block/namespace_test.go | 234 +++++++ block/params/block.go | 81 +++ block/path.go | 119 ++++ block/path_test.go | 156 +++++ block/s3/adapter.go | 814 ++++++++++++++++++++++ block/s3/adapter_test.go | 86 +++ block/s3/client_cache.go | 147 ++++ block/s3/client_cache_test.go | 114 +++ block/s3/extract_sse.go | 84 +++ block/s3/main_test.go | 86 +++ block/s3/stats.go | 31 + block/s3/testdata/chunk250_data500.input | 1 + block/s3/testdata/chunk250_data500.output | 6 + block/s3/testdata/chunk250_data510.input | 1 + block/s3/testdata/chunk250_data510.output | 8 + block/s3/testdata/chunk3000_data10.input | 1 + block/s3/testdata/chunk3000_data10.output | 4 + block/s3/testdata/chunk5_data0.input | 0 block/s3/testdata/chunk5_data0.output | 2 + block/s3/testdata/chunk5_data10.input | 1 + block/s3/testdata/chunk5_data10.output | 6 + block/s3/testdata/chunk600_data240.input | 1 + block/s3/testdata/chunk600_data240.output | 4 + block/s3/walker.go | 97 +++ block/transient/adapter.go | 160 +++++ block/walker.go | 79 +++ cmd/daemon.go | 8 + cmd/init.go | 14 +- config/blockstore.go | 162 +++++ config/config.go | 5 +- config/default.go | 17 + go.mod | 120 +++- go.sum | 236 ++++++- 63 files changed, 7855 insertions(+), 27 deletions(-) create mode 100644 block/adapter.go create mode 100644 block/azure/adapter.go create mode 100644 block/azure/adapter_test.go create mode 100644 block/azure/chunkwriting.go create mode 100644 block/azure/client_cache.go create mode 100644 block/azure/client_test.go create mode 100644 block/azure/main_test.go create mode 100644 block/azure/multipart_block_writer.go create mode 100644 block/azure/stats.go create mode 100644 block/azure/walker.go create mode 100644 block/blocktest/adapter.go create mode 100644 block/errors.go create mode 100644 block/factory/build.go create mode 100644 block/gs/adapter.go create mode 100644 block/gs/adapter_test.go create mode 100644 block/gs/compose.go create mode 100644 block/gs/compose_test.go create mode 100644 block/gs/main_test.go create mode 100644 block/gs/stats.go create mode 100644 block/gs/walker.go create mode 100644 block/hashing_reader.go create mode 100644 block/local/adapter.go create mode 100644 block/local/adapter_test.go create mode 100644 block/local/etag_test.go create mode 100644 block/local/walker.go create mode 100644 block/mem/adapter.go create mode 100644 block/mem/adapter_test.go create mode 100644 block/namespace.go create mode 100644 block/namespace_test.go create mode 100644 block/params/block.go create mode 100644 block/path.go create mode 100644 block/path_test.go create mode 100644 block/s3/adapter.go create mode 100644 block/s3/adapter_test.go create mode 100644 block/s3/client_cache.go create mode 100644 block/s3/client_cache_test.go create mode 100644 block/s3/extract_sse.go create mode 100644 block/s3/main_test.go create mode 100644 block/s3/stats.go create mode 100755 block/s3/testdata/chunk250_data500.input create mode 100755 block/s3/testdata/chunk250_data500.output create mode 100755 block/s3/testdata/chunk250_data510.input create mode 100755 block/s3/testdata/chunk250_data510.output create mode 100755 block/s3/testdata/chunk3000_data10.input create mode 100755 block/s3/testdata/chunk3000_data10.output create mode 100755 block/s3/testdata/chunk5_data0.input create mode 100755 block/s3/testdata/chunk5_data0.output create mode 100755 block/s3/testdata/chunk5_data10.input create mode 100755 block/s3/testdata/chunk5_data10.output create mode 100755 block/s3/testdata/chunk600_data240.input create mode 100755 block/s3/testdata/chunk600_data240.output create mode 100644 block/s3/walker.go create mode 100644 block/transient/adapter.go create mode 100644 block/walker.go create mode 100644 config/blockstore.go diff --git a/.fend.yaml b/.fend.yaml index 1526c31d..53318ff1 100644 --- a/.fend.yaml +++ b/.fend.yaml @@ -4,3 +4,6 @@ skip: - .vscode file: - jzfs + extension: + - .input + - .output diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5b1e3133..77abde4e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,6 +32,11 @@ jobs: run: | make build + - uses: docker-practice/actions-setup-docker@master + timeout-minutes: 12 + - run: | + docker version + - name: Test run: | go test -coverpkg=./... -coverprofile=coverage.out -covermode=atomic -timeout=30m -parallel=4 -v ./... diff --git a/block/adapter.go b/block/adapter.go new file mode 100644 index 00000000..249c9dee --- /dev/null +++ b/block/adapter.go @@ -0,0 +1,154 @@ +package block + +import ( + "context" + "io" + "net/http" + "net/url" + "time" +) + +// MultipartPart single multipart information +type MultipartPart struct { + ETag string + PartNumber int +} + +// MultipartUploadCompletion parts described as part of complete multipart upload. Each part holds the part number and ETag received while calling part upload. +// NOTE that S3 implementation and our S3 gateway accept and returns ETag value surrounded with double-quotes ("), while +// the adapter implementations supply the raw value of the etag (without double quotes) and let the gateway manage the s3 +// protocol specifications. +type MultipartUploadCompletion struct { + Part []MultipartPart +} + +// IdentifierType is the type the ObjectPointer Identifier +type IdentifierType int32 + +// PreSignMode is the mode to use when generating a pre-signed URL (read/write) +type PreSignMode int32 + +const ( + BlockstoreTypeS3 = "s3" + BlockstoreTypeGS = "gs" + BlockstoreTypeAzure = "azure" + BlockstoreTypeLocal = "local" + BlockstoreTypeMem = "mem" + BlockstoreTypeTransient = "transient" +) + +const ( + // IdentifierTypeRelative indicates that the address is relative to the storage namespace. + // For example: "/foo/bar" + IdentifierTypeRelative IdentifierType = 1 + + // IdentifierTypeFull indicates that the address is the full address of the object in the object store. + // For example: "s3://bucket/foo/bar" + IdentifierTypeFull IdentifierType = 2 +) + +const ( + PreSignModeRead PreSignMode = iota + PreSignModeWrite +) + +// DefaultPreSignExpiryDuration is the amount of time pre-signed requests are valid for. +const DefaultPreSignExpiryDuration = 15 * time.Minute + +// ObjectPointer is a unique identifier of an object in the object +// store: the store is a 1:1 mapping between pointers and objects. +type ObjectPointer struct { + StorageNamespace string + Identifier string + + // Indicates whether the Identifier is relative to the StorageNamespace, + // full address to an object, or unknown. + IdentifierType IdentifierType +} + +// PutOpts contains optional arguments for Put. These should be +// analogous to options on some underlying storage layer. Missing +// arguments are mapped to the default if a storage layer implements +// the option. +// +// If the same Put command is implemented multiple times with the same +// contents but different option values, the first supplied option +// value is retained. +type PutOpts struct { + StorageClass *string // S3 storage class +} + +// WalkOpts is a unique identifier of a prefix in the object store. +type WalkOpts struct { + StorageNamespace string + Prefix string +} + +// CreateMultiPartUploadResponse multipart upload ID and additional headers (implementation specific) currently it targets s3 +// capabilities to enable encryption properties +type CreateMultiPartUploadResponse struct { + UploadID string + ServerSideHeader http.Header +} + +// CompleteMultiPartUploadResponse complete multipart etag, content length and additional headers (implementation specific) currently it targets s3. +// The ETag is a hex string value of the content checksum +type CompleteMultiPartUploadResponse struct { + ETag string + ContentLength int64 + ServerSideHeader http.Header +} + +// UploadPartResponse upload part ETag and additional headers (implementation specific) currently it targets s3 +// capabilities to enable encryption properties +// The ETag is a hex string value of the content checksum +type UploadPartResponse struct { + ETag string + ServerSideHeader http.Header +} + +// CreateMultiPartUploadOpts contains optional arguments for +// CreateMultiPartUpload. These should be analogous to options on +// some underlying storage layer. Missing arguments are mapped to the +// default if a storage layer implements the option. +// +// If the same CreateMultiPartUpload command is implemented multiple times with the same +// contents but different option values, the first supplied option +// value is retained. +type CreateMultiPartUploadOpts struct { + StorageClass *string // S3 storage class +} + +// Properties of an object stored on the underlying block store. +// Refer to the actual underlying Adapter for which properties are +// actually reported. +type Properties struct { + StorageClass *string +} + +type Adapter interface { + Put(ctx context.Context, obj ObjectPointer, sizeBytes int64, reader io.Reader, opts PutOpts) error + Get(ctx context.Context, obj ObjectPointer, expectedSize int64) (io.ReadCloser, error) + GetWalker(uri *url.URL) (Walker, error) + + // GetPreSignedURL returns a pre-signed URL for accessing obj with mode, and the + // expiry time for this URL. The expiry time IsZero() if reporting + // expiry is not supported. The expiry time will be sooner than + // Config.*.PreSignedExpiry if an auth token is about to expire. + GetPreSignedURL(ctx context.Context, obj ObjectPointer, mode PreSignMode) (string, time.Time, error) + Exists(ctx context.Context, obj ObjectPointer) (bool, error) + GetRange(ctx context.Context, obj ObjectPointer, startPosition int64, endPosition int64) (io.ReadCloser, error) + GetProperties(ctx context.Context, obj ObjectPointer) (Properties, error) + Remove(ctx context.Context, obj ObjectPointer) error + Copy(ctx context.Context, sourceObj, destinationObj ObjectPointer) error + CreateMultiPartUpload(ctx context.Context, obj ObjectPointer, r *http.Request, opts CreateMultiPartUploadOpts) (*CreateMultiPartUploadResponse, error) + UploadPart(ctx context.Context, obj ObjectPointer, sizeBytes int64, reader io.Reader, uploadID string, partNumber int) (*UploadPartResponse, error) + UploadCopyPart(ctx context.Context, sourceObj, destinationObj ObjectPointer, uploadID string, partNumber int) (*UploadPartResponse, error) + UploadCopyPartRange(ctx context.Context, sourceObj, destinationObj ObjectPointer, uploadID string, partNumber int, startPosition, endPosition int64) (*UploadPartResponse, error) + AbortMultiPartUpload(ctx context.Context, obj ObjectPointer, uploadID string) error + CompleteMultiPartUpload(ctx context.Context, obj ObjectPointer, uploadID string, multipartList *MultipartUploadCompletion) (*CompleteMultiPartUploadResponse, error) + BlockstoreType() string + GetStorageNamespaceInfo() StorageNamespaceInfo + ResolveNamespace(storageNamespace, key string, identifierType IdentifierType) (QualifiedKey, error) + RuntimeStats() map[string]string +} diff --git a/block/azure/adapter.go b/block/azure/adapter.go new file mode 100644 index 00000000..e57870fa --- /dev/null +++ b/block/azure/adapter.go @@ -0,0 +1,593 @@ +package azure + +import ( + "context" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blockblob" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas" + "github.com/jiaozifs/jiaozifs/block" + "github.com/jiaozifs/jiaozifs/block/params" +) + +const ( + sizeSuffix = "_size" + idSuffix = "_id" + _1MiB = 1024 * 1024 + MaxBuffers = 1 + // udcCacheSize - Arbitrary number: exceeding this number means that in the expiry timeframe we requested pre-signed urls from + // more the 5000 different accounts, which is highly unlikely + udcCacheSize = 5000 + + BlobEndpointFormat = "https://%s.blob.core.windows.net/" +) + +type Adapter struct { + clientCache *ClientCache + preSignedExpiry time.Duration + disablePreSigned bool + disablePreSignedUI bool +} + +func NewAdapter(_ context.Context, params params.Azure) (*Adapter, error) { + log.With("type", "azure").Info("initialized blockstore adapter") + preSignedExpiry := params.PreSignedExpiry + if preSignedExpiry == 0 { + preSignedExpiry = block.DefaultPreSignExpiryDuration + } + cache, err := NewCache(params) + if err != nil { + return nil, err + } + return &Adapter{ + clientCache: cache, + preSignedExpiry: preSignedExpiry, + disablePreSigned: params.DisablePreSigned, + disablePreSignedUI: params.DisablePreSignedUI, + }, nil +} + +type BlobURLInfo struct { + StorageAccountName string + ContainerURL string + ContainerName string + BlobURL string +} + +type PrefixURLInfo struct { + StorageAccountName string + ContainerURL string + ContainerName string + Prefix string +} + +func ExtractStorageAccount(storageAccount *url.URL) (string, error) { + // In azure the subdomain is the storage account + const expectedHostParts = 2 + hostParts := strings.SplitN(storageAccount.Host, ".", expectedHostParts) + if len(hostParts) != expectedHostParts { + return "", fmt.Errorf("wrong host parts(%d): %w", len(hostParts), block.ErrInvalidAddress) + } + + return hostParts[0], nil +} + +func ResolveBlobURLInfoFromURL(pathURL *url.URL) (BlobURLInfo, error) { + var qk BlobURLInfo + err := block.ValidateStorageType(pathURL, block.StorageTypeAzure) + if err != nil { + return qk, err + } + + // In azure, the first part of the path is part of the storage namespace + trimmedPath := strings.Trim(pathURL.Path, "/") + pathParts := strings.Split(trimmedPath, "/") + if len(pathParts) == 0 { + return qk, fmt.Errorf("wrong path parts(%d): %w", len(pathParts), block.ErrInvalidAddress) + } + + storageAccount, err := ExtractStorageAccount(pathURL) + if err != nil { + return qk, err + } + + return BlobURLInfo{ + StorageAccountName: storageAccount, + ContainerURL: fmt.Sprintf("%s://%s/%s", pathURL.Scheme, pathURL.Host, pathParts[0]), + ContainerName: pathParts[0], + BlobURL: strings.Join(pathParts[1:], "/"), + }, nil +} + +func resolveBlobURLInfo(obj block.ObjectPointer) (BlobURLInfo, error) { + key := obj.Identifier + defaultNamespace := obj.StorageNamespace + var qk BlobURLInfo + // check if the key is fully qualified + parsedKey, err := url.ParseRequestURI(key) + if err != nil { + // is not fully qualified, treat as key only + // if we don't have a trailing slash for the namespace, add it. + parsedNamespace, err := url.ParseRequestURI(defaultNamespace) + if err != nil { + return qk, err + } + qp, err := ResolveBlobURLInfoFromURL(parsedNamespace) + if err != nil { + return qk, err + } + info := BlobURLInfo{ + StorageAccountName: qp.StorageAccountName, + ContainerURL: qp.ContainerURL, + ContainerName: qp.ContainerName, + BlobURL: qp.BlobURL + "/" + key, + } + if qp.BlobURL == "" { + info.BlobURL = key + } + return info, nil + } + return ResolveBlobURLInfoFromURL(parsedKey) +} + +func (a *Adapter) translatePutOpts(_ context.Context, opts block.PutOpts) azblob.UploadStreamOptions { + res := azblob.UploadStreamOptions{} + if opts.StorageClass == nil { + return res + } + + for _, t := range blob.PossibleAccessTierValues() { + if strings.EqualFold(*opts.StorageClass, string(t)) { + accessTier := t + res.AccessTier = &accessTier + break + } + } + + if res.AccessTier == nil { + log.With("tier_type", *opts.StorageClass).Warn("Unknown Azure tier type") + } + + return res +} + +func (a *Adapter) Put(ctx context.Context, obj block.ObjectPointer, sizeBytes int64, reader io.Reader, opts block.PutOpts) error { + var err error + defer reportMetrics("Put", time.Now(), &sizeBytes, &err) + qualifiedKey, err := resolveBlobURLInfo(obj) + if err != nil { + return err + } + o := a.translatePutOpts(ctx, opts) + containerClient, err := a.clientCache.NewContainerClient(qualifiedKey.StorageAccountName, qualifiedKey.ContainerName) + if err != nil { + return err + } + _, err = containerClient.NewBlockBlobClient(qualifiedKey.BlobURL).UploadStream(ctx, reader, &o) + return err +} + +func (a *Adapter) Get(ctx context.Context, obj block.ObjectPointer, _ int64) (io.ReadCloser, error) { + var err error + defer reportMetrics("Get", time.Now(), nil, &err) + + return a.Download(ctx, obj, 0, blockblob.CountToEnd) +} + +func (a *Adapter) GetWalker(uri *url.URL) (block.Walker, error) { + if err := block.ValidateStorageType(uri, block.StorageTypeAzure); err != nil { + return nil, err + } + + storageAccount, err := ExtractStorageAccount(uri) + if err != nil { + return nil, err + } + + client, err := a.clientCache.NewServiceClient(storageAccount) + if err != nil { + return nil, err + } + + return NewAzureBlobWalker(client) +} + +func (a *Adapter) GetPreSignedURL(ctx context.Context, obj block.ObjectPointer, mode block.PreSignMode) (string, time.Time, error) { + if a.disablePreSigned { + return "", time.Time{}, block.ErrOperationNotSupported + } + + permissions := sas.BlobPermissions{Read: true} + if mode == block.PreSignModeWrite { + permissions = sas.BlobPermissions{ + Read: true, + Add: true, + Write: true, + } + } + preSignedURL, err := a.getPreSignedURL(ctx, obj, permissions) + // TODO(#6347): Report expiry. + return preSignedURL, time.Time{}, err +} + +func (a *Adapter) getPreSignedURL(ctx context.Context, obj block.ObjectPointer, permissions sas.BlobPermissions) (string, error) { + if a.disablePreSigned { + return "", block.ErrOperationNotSupported + } + + qualifiedKey, err := resolveBlobURLInfo(obj) + if err != nil { + return "", err + } + + // Use shared credential for clients initialized with storage access key + if qualifiedKey.StorageAccountName == a.clientCache.params.StorageAccount && a.clientCache.params.StorageAccessKey != "" { + container, err := a.clientCache.NewContainerClient(qualifiedKey.StorageAccountName, qualifiedKey.ContainerName) + if err != nil { + return "", err + } + client := container.NewBlobClient(qualifiedKey.BlobURL) + urlExpiry := a.newPreSignedTime() + return client.GetSASURL(permissions, urlExpiry, &blob.GetSASURLOptions{}) + } + + // Otherwise assume using role based credentials and build signed URL using user delegation credentials + urlExpiry := a.newPreSignedTime() + udc, err := a.clientCache.NewUDC(ctx, qualifiedKey.StorageAccountName, &urlExpiry) + if err != nil { + return "", err + } + + // Create Blob Signature Values with desired permissions and sign with user delegation credential + blobSignatureValues := sas.BlobSignatureValues{ + Protocol: sas.ProtocolHTTPS, + ExpiryTime: urlExpiry, + Permissions: to.Ptr(permissions).String(), + ContainerName: qualifiedKey.ContainerName, + BlobName: qualifiedKey.BlobURL, + } + sasQueryParams, err := blobSignatureValues.SignWithUserDelegation(udc) + if err != nil { + return "", err + } + + // format blob URL with signed SAS query params + accountEndpoint := fmt.Sprintf(BlobEndpointFormat, qualifiedKey.StorageAccountName) + u, err := url.JoinPath(accountEndpoint, qualifiedKey.ContainerName, qualifiedKey.BlobURL) + if err != nil { + return "", err + } + u += "?" + sasQueryParams.Encode() + return u, nil +} + +func (a *Adapter) GetRange(ctx context.Context, obj block.ObjectPointer, startPosition int64, endPosition int64) (io.ReadCloser, error) { + var err error + defer reportMetrics("GetRange", time.Now(), nil, &err) + + return a.Download(ctx, obj, startPosition, endPosition-startPosition+1) +} + +func (a *Adapter) Download(ctx context.Context, obj block.ObjectPointer, offset, count int64) (io.ReadCloser, error) { + qualifiedKey, err := resolveBlobURLInfo(obj) + if err != nil { + return nil, err + } + container, err := a.clientCache.NewContainerClient(qualifiedKey.StorageAccountName, qualifiedKey.ContainerName) + if err != nil { + return nil, err + } + blobURL := container.NewBlockBlobClient(qualifiedKey.BlobURL) + + downloadResponse, err := blobURL.DownloadStream(ctx, &azblob.DownloadStreamOptions{ + RangeGetContentMD5: nil, + Range: blob.HTTPRange{ + Offset: offset, + Count: count, + }, + }) + if bloberror.HasCode(err, bloberror.BlobNotFound) { + return nil, block.ErrDataNotFound + } + if err != nil { + log.Errorf("failed to get azure blob from container %s key %s %v", container, blobURL, err) + return nil, err + } + return downloadResponse.Body, nil +} + +func (a *Adapter) Exists(ctx context.Context, obj block.ObjectPointer) (bool, error) { + var err error + defer reportMetrics("Exists", time.Now(), nil, &err) + + qualifiedKey, err := resolveBlobURLInfo(obj) + if err != nil { + return false, err + } + + containerClient, err := a.clientCache.NewContainerClient(qualifiedKey.StorageAccountName, qualifiedKey.ContainerName) + if err != nil { + return false, err + } + blobURL := containerClient.NewBlobClient(qualifiedKey.BlobURL) + + _, err = blobURL.GetProperties(ctx, nil) + + if bloberror.HasCode(err, bloberror.BlobNotFound) { + return false, nil + } + if err != nil { + return false, err + } + return true, nil +} + +func (a *Adapter) GetProperties(ctx context.Context, obj block.ObjectPointer) (block.Properties, error) { + var err error + defer reportMetrics("GetProperties", time.Now(), nil, &err) + + qualifiedKey, err := resolveBlobURLInfo(obj) + if err != nil { + return block.Properties{}, err + } + + containerClient, err := a.clientCache.NewContainerClient(qualifiedKey.StorageAccountName, qualifiedKey.ContainerName) + if err != nil { + return block.Properties{}, err + } + blobURL := containerClient.NewBlobClient(qualifiedKey.BlobURL) + + props, err := blobURL.GetProperties(ctx, nil) + if err != nil { + return block.Properties{}, err + } + return block.Properties{StorageClass: props.AccessTier}, nil +} + +func (a *Adapter) Remove(ctx context.Context, obj block.ObjectPointer) error { + var err error + defer reportMetrics("Remove", time.Now(), nil, &err) + qualifiedKey, err := resolveBlobURLInfo(obj) + if err != nil { + return err + } + containerClient, err := a.clientCache.NewContainerClient(qualifiedKey.StorageAccountName, qualifiedKey.ContainerName) + if err != nil { + return err + } + blobURL := containerClient.NewBlobClient(qualifiedKey.BlobURL) + + _, err = blobURL.Delete(ctx, nil) + return err +} + +func (a *Adapter) Copy(ctx context.Context, sourceObj, destinationObj block.ObjectPointer) error { + var err error + defer reportMetrics("Copy", time.Now(), nil, &err) + + qualifiedDestinationKey, err := resolveBlobURLInfo(destinationObj) + if err != nil { + return err + } + + destContainerClient, err := a.clientCache.NewContainerClient(qualifiedDestinationKey.StorageAccountName, qualifiedDestinationKey.ContainerName) + if err != nil { + return err + } + destClient := destContainerClient.NewBlobClient(qualifiedDestinationKey.BlobURL) + + sasKey, _, err := a.GetPreSignedURL(ctx, sourceObj, block.PreSignModeRead) + if err != nil { + return err + } + + // Optimistic flow - try to copy synchronously + _, err = destClient.CopyFromURL(ctx, sasKey, nil) + if err == nil { + return nil + } + // Azure API (backend) returns ambiguous error code which requires us to parse the error message to understand what is the nature of the error + // See: https://github.com/Azure/azure-sdk-for-go/issues/19880 + if !bloberror.HasCode(err, bloberror.CannotVerifyCopySource) || + !strings.Contains(err.Error(), "The source request body for synchronous copy is too large and exceeds the maximum permissible limit") { + return err + } + + // Blob too big for synchronous copy. Perform async copy + logger := log.With( + "sourceObj", sourceObj.Identifier, + "destObj", destinationObj.Identifier, + ) + logger.Debug("Perform async copy") + res, err := destClient.StartCopyFromURL(ctx, sasKey, nil) + if err != nil { + return err + } + copyStatus := res.CopyStatus + if copyStatus == nil { + return fmt.Errorf("%w: failed to get copy status", block.ErrAsyncCopyFailed) + } + + progress := "" + const asyncPollInterval = 5 * time.Second + for { + select { + case <-ctx.Done(): + log.With("copy_progress", progress).Warn("context canceled, aborting copy") + // Context canceled - perform abort on copy use a different context for the abort + _, err := destClient.AbortCopyFromURL(context.Background(), *res.CopyID, nil) + if err != nil { + log.Errorf("failed to abort copy %v", err) + } + return ctx.Err() + + case <-time.After(asyncPollInterval): + p, err := destClient.GetProperties(ctx, nil) + if err != nil { + return err + } + copyStatus = p.CopyStatus + if copyStatus == nil { + return fmt.Errorf("%w: failed to get copy status", block.ErrAsyncCopyFailed) + } + progress = *p.CopyProgress + switch *copyStatus { + case blob.CopyStatusTypeSuccess: + log.With("object_properties", p).Debug("Async copy successful") + return nil + + case blob.CopyStatusTypeAborted: + return fmt.Errorf("%w: unexpected abort", block.ErrAsyncCopyFailed) + + case blob.CopyStatusTypeFailed: + return fmt.Errorf("%w: copy status failed", block.ErrAsyncCopyFailed) + + case blob.CopyStatusTypePending: + log.With("copy_progress", progress).Debug("Copy pending") + + default: + return fmt.Errorf("%w: invalid copy status: %s", block.ErrAsyncCopyFailed, *copyStatus) + } + } + } +} + +func (a *Adapter) CreateMultiPartUpload(_ context.Context, obj block.ObjectPointer, _ *http.Request, _ block.CreateMultiPartUploadOpts) (*block.CreateMultiPartUploadResponse, error) { + // Azure has no create multipart upload + var err error + defer reportMetrics("CreateMultiPartUpload", time.Now(), nil, &err) + + qualifiedKey, err := resolveBlobURLInfo(obj) + if err != nil { + return nil, err + } + + return &block.CreateMultiPartUploadResponse{ + UploadID: qualifiedKey.BlobURL, + }, nil +} + +func (a *Adapter) UploadPart(ctx context.Context, obj block.ObjectPointer, _ int64, reader io.Reader, _ string, _ int) (*block.UploadPartResponse, error) { + var err error + defer reportMetrics("UploadPart", time.Now(), nil, &err) + + qualifiedKey, err := resolveBlobURLInfo(obj) + if err != nil { + return nil, err + } + + container, err := a.clientCache.NewContainerClient(qualifiedKey.StorageAccountName, qualifiedKey.ContainerName) + if err != nil { + return nil, err + } + hashReader := block.NewHashingReader(reader, block.HashFunctionMD5) + + multipartBlockWriter := NewMultipartBlockWriter(hashReader, *container, qualifiedKey.BlobURL) + _, err = copyFromReader(ctx, hashReader, multipartBlockWriter, blockblob.UploadStreamOptions{ + BlockSize: _1MiB, + Concurrency: MaxBuffers, + }) + if err != nil { + return nil, err + } + return &block.UploadPartResponse{ + ETag: strings.Trim(multipartBlockWriter.etag, `"`), + }, nil +} + +func (a *Adapter) UploadCopyPart(ctx context.Context, sourceObj, destinationObj block.ObjectPointer, _ string, _ int) (*block.UploadPartResponse, error) { + var err error + defer reportMetrics("UploadPart", time.Now(), nil, &err) + + return a.copyPartRange(ctx, sourceObj, destinationObj, 0, blockblob.CountToEnd) +} + +func (a *Adapter) UploadCopyPartRange(ctx context.Context, sourceObj, destinationObj block.ObjectPointer, _ string, _ int, startPosition, endPosition int64) (*block.UploadPartResponse, error) { + var err error + defer reportMetrics("UploadPart", time.Now(), nil, &err) + return a.copyPartRange(ctx, sourceObj, destinationObj, startPosition, endPosition-startPosition+1) +} + +func (a *Adapter) copyPartRange(ctx context.Context, sourceObj, destinationObj block.ObjectPointer, startPosition, count int64) (*block.UploadPartResponse, error) { + qualifiedSourceKey, err := resolveBlobURLInfo(sourceObj) + if err != nil { + return nil, err + } + + qualifiedDestinationKey, err := resolveBlobURLInfo(destinationObj) + if err != nil { + return nil, err + } + + destinationContainer, err := a.clientCache.NewContainerClient(qualifiedDestinationKey.StorageAccountName, qualifiedDestinationKey.ContainerName) + if err != nil { + return nil, err + } + sourceContainer, err := a.clientCache.NewContainerClient(qualifiedSourceKey.StorageAccountName, qualifiedSourceKey.ContainerName) + if err != nil { + return nil, err + } + + sourceBlobURL := sourceContainer.NewBlockBlobClient(qualifiedSourceKey.BlobURL) + + return copyPartRange(ctx, *destinationContainer, qualifiedDestinationKey.BlobURL, *sourceBlobURL, startPosition, count) +} + +func (a *Adapter) AbortMultiPartUpload(_ context.Context, _ block.ObjectPointer, _ string) error { + // Azure has no abort. In case of commit, uncommitted parts are erased. Otherwise, staged data is erased after 7 days + return nil +} + +func (a *Adapter) BlockstoreType() string { + return block.BlockstoreTypeAzure +} + +func (a *Adapter) CompleteMultiPartUpload(ctx context.Context, obj block.ObjectPointer, _ string, multipartList *block.MultipartUploadCompletion) (*block.CompleteMultiPartUploadResponse, error) { + var err error + defer reportMetrics("CompleteMultiPartUpload", time.Now(), nil, &err) + qualifiedKey, err := resolveBlobURLInfo(obj) + if err != nil { + return nil, err + } + containerURL, err := a.clientCache.NewContainerClient(qualifiedKey.StorageAccountName, qualifiedKey.ContainerName) + if err != nil { + return nil, err + } + + return completeMultipart(ctx, multipartList.Part, *containerURL, qualifiedKey.BlobURL) +} + +func (a *Adapter) GetStorageNamespaceInfo() block.StorageNamespaceInfo { + info := block.DefaultStorageNamespaceInfo(block.BlockstoreTypeAzure) + info.ImportValidityRegex = `^https?://[a-z0-9_-]+\.(blob|adls)\.core\.windows\.net` // added adls for import hint validation in UI + info.ValidityRegex = `^https?://[a-z0-9_-]+\.blob\.core\.windows\.net` + info.Example = "https://mystorageaccount.blob.core.windows.net/mycontainer/" + if a.disablePreSigned { + info.PreSignSupport = false + } + if !(a.disablePreSignedUI || a.disablePreSigned) { + info.PreSignSupportUI = true + } + return info +} + +func (a *Adapter) ResolveNamespace(storageNamespace, key string, identifierType block.IdentifierType) (block.QualifiedKey, error) { + return block.DefaultResolveNamespace(storageNamespace, key, identifierType) +} + +func (a *Adapter) RuntimeStats() map[string]string { + return nil +} + +func (a *Adapter) newPreSignedTime() time.Time { + return time.Now().UTC().Add(a.preSignedExpiry) +} diff --git a/block/azure/adapter_test.go b/block/azure/adapter_test.go new file mode 100644 index 00000000..f9c7eeb2 --- /dev/null +++ b/block/azure/adapter_test.go @@ -0,0 +1,84 @@ +package azure_test + +import ( + "context" + "net/url" + "regexp" + "testing" + + "github.com/jiaozifs/jiaozifs/block/azure" + "github.com/jiaozifs/jiaozifs/block/blocktest" + "github.com/jiaozifs/jiaozifs/block/params" + "github.com/stretchr/testify/require" +) + +func TestAzureAdapter(t *testing.T) { + basePath, err := url.JoinPath(blockURL, containerName) + require.NoError(t, err) + localPath, err := url.JoinPath(basePath, "lakefs") + require.NoError(t, err) + externalPath, err := url.JoinPath(basePath, "external") + require.NoError(t, err) + + adapter, err := azure.NewAdapter(context.Background(), params.Azure{ + StorageAccount: accountName, + StorageAccessKey: accountKey, + TestEndpointURL: blockURL, + }) + require.NoError(t, err, "create new adapter") + blocktest.AdapterTest(t, adapter, localPath, externalPath) +} + +func TestAdapterNamespace(t *testing.T) { + adapter, err := azure.NewAdapter(context.Background(), params.Azure{ + StorageAccount: accountName, + StorageAccessKey: accountKey, + TestEndpointURL: blockURL, + }) + require.NoError(t, err, "create new adapter") + + expr, err := regexp.Compile(adapter.GetStorageNamespaceInfo().ValidityRegex) + require.NoError(t, err) + + tests := []struct { + Name string + Namespace string + Success bool + }{ + { + Name: "valid_https", + Namespace: "https://test.blob.core.windows.net/container1/repo1", + Success: true, + }, + { + Name: "valid_http", + Namespace: "http://test.blob.core.windows.net/container1/repo1", + Success: true, + }, + { + Name: "invalid_subdomain", + Namespace: "https://test.adls.core.windows.net/container1/repo1", + Success: false, + }, + { + Name: "partial", + Namespace: "https://test.adls.core.windows.n", + Success: false, + }, + { + Name: "s3", + Namespace: "s3://test/adls/core/windows/net", + Success: false, + }, + { + Name: "invalid_string", + Namespace: "this is a bad string", + Success: false, + }, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + require.Equal(t, tt.Success, expr.MatchString(tt.Namespace)) + }) + } +} diff --git a/block/azure/chunkwriting.go b/block/azure/chunkwriting.go new file mode 100644 index 00000000..b81c910b --- /dev/null +++ b/block/azure/chunkwriting.go @@ -0,0 +1,335 @@ +package azure + +import ( + "bytes" + "context" + "encoding/base64" + "encoding/binary" + "errors" + "fmt" + "io" + "sync" + "sync/atomic" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blockblob" + guuid "github.com/google/uuid" +) + +var ErrEmptyBuffer = errors.New("BufferManager returned a 0 size buffer, this is a bug in the manager") + +// This code adapted from azblob chunkwriting.go +// The reason is that the original code commit the data at the end of the copy +// In order to support multipart upload we need to save the blockIDs instead of committing them +// And once complete multipart is called we commit all the blockIDs + +// blockWriter provides methods to upload blocks that represent a file to a server and commit them. +// This allows us to provide a local implementation that fakes the server for hermetic testing. +type blockWriter interface { + StageBlock(context.Context, string, io.ReadSeekCloser, *blockblob.StageBlockOptions) (blockblob.StageBlockResponse, error) + Upload(context.Context, io.ReadSeekCloser, *blockblob.UploadOptions) (blockblob.UploadResponse, error) + CommitBlockList(context.Context, []string, *blockblob.CommitBlockListOptions) (blockblob.CommitBlockListResponse, error) +} + +// copyFromReader copies a source io.Reader to blob storage using concurrent uploads. +func copyFromReader(ctx context.Context, from io.Reader, to blockWriter, o blockblob.UploadStreamOptions) (*blockblob.CommitBlockListResponse, error) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + buffers := newMMBPool(o.Concurrency, o.BlockSize) + defer buffers.Free() + + cp := &copier{ + ctx: ctx, + cancel: cancel, + reader: from, + to: to, + id: newID(), + o: o, + errCh: make(chan error, 1), + buffers: buffers, + } + + // Send all our chunks until we get an error. + var ( + err error + buffer []byte + ) + for { + select { + case buffer = <-buffers.Acquire(): + // got a buffer + default: + // no buffer available; allocate a new buffer if possible + buffers.Grow() + // either grab the newly allocated buffer or wait for one to become available + buffer = <-buffers.Acquire() + } + err = cp.sendChunk(buffer) + if err != nil { + break + } + } + cp.wg.Wait() + // If the error is not EOF, then we have a problem. + if err != nil && !errors.Is(err, io.EOF) { + return nil, err + } + + // Close out our upload. + if err := cp.close(); err != nil { + return nil, err + } + + return &cp.result, nil +} + +// copier streams a file via chunks in parallel from a reader representing a file. +// Do not use directly, instead use copyFromReader(). +type copier struct { + // ctx holds the context of a copier. This is normally a faux pas to store a Context in a struct. In this case, + // the copier has the lifetime of a function call, so it's fine. + ctx context.Context + cancel context.CancelFunc + + // o contains our options for uploading. + o blockblob.UploadStreamOptions + + // id provides the ids for each chunk. + id *id + + // reader is the source to be written to storage. + reader io.Reader + // to is the location we are writing our chunks to. + to blockWriter + + // errCh is used to hold the first error from our concurrent writers. + errCh chan error + // wg provides a count of how many writers we are waiting to finish. + wg sync.WaitGroup + + // result holds the final result from blob storage after we have submitted all chunks. + result blockblob.CommitBlockListResponse + + buffers bufferManager[mmb] +} + +type copierChunk struct { + buffer []byte + id string +} + +// getErr returns an error by priority. First, if a function set an error, it returns that error. Next, if the Context has an error +// it returns that error. Otherwise it is nil. getErr supports only returning an error once per copier. +func (c *copier) getErr() error { + select { + case err := <-c.errCh: + return err + default: + } + return c.ctx.Err() +} + +// sendChunk reads data from out internal reader, creates a chunk, and sends it to be written via a channel. +// sendChunk returns io.EOF when the reader returns an io.EOF or io.ErrUnexpectedEOF. +func (c *copier) sendChunk(buffer []byte) error { + // TODO(niro): Need to find a solution to all the buffers.Release + if err := c.getErr(); err != nil { + c.buffers.Release(buffer) + return err + } + + if len(buffer) == 0 { + c.buffers.Release(buffer) + return ErrEmptyBuffer + } + + n, err := io.ReadFull(c.reader, buffer) + switch { + case err == nil && n == 0: + c.buffers.Release(buffer) + return nil + + case err == nil: + nextID := c.id.next() + c.wg.Add(1) + // NOTE: we must pass id as an arg to our goroutine else + // it's captured by reference and can change underneath us! + go func(nextID string) { + // signal that the block has been staged. + // we MUST do this after attempting to write to errCh + // to avoid it racing with the reading goroutine. + defer c.wg.Done() + defer c.buffers.Release(buffer) + // Upload the outgoing block, matching the number of bytes read + c.write(copierChunk{buffer: buffer[0:n], id: nextID}) + }(nextID) + return nil + + case err != nil && (errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF)) && n == 0: + c.buffers.Release(buffer) + return io.EOF + } + + if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) { + nextID := c.id.next() + c.wg.Add(1) + go func(nextID string) { + defer c.wg.Done() + defer c.buffers.Release(buffer) + // Upload the outgoing block, matching the number of bytes read + c.write(copierChunk{buffer: buffer[0:n], id: nextID}) + }(nextID) + return io.EOF + } + if err := c.getErr(); err != nil { + c.buffers.Release(buffer) + return err + } + c.buffers.Release(buffer) + return err +} + +// write uploads a chunk to blob storage. +func (c *copier) write(chunk copierChunk) { + if err := c.ctx.Err(); err != nil { + return + } + _, err := c.to.StageBlock(c.ctx, chunk.id, streaming.NopCloser(bytes.NewReader(chunk.buffer)), &blockblob.StageBlockOptions{ + CPKInfo: c.o.CPKInfo, + CPKScopeInfo: c.o.CPKScopeInfo, + TransactionalValidation: c.o.TransactionalValidation, + }) + if err != nil { + c.errCh <- fmt.Errorf("write error: %w", err) + return + } +} + +// close commits our blocks to blob storage and closes our writer. +func (c *copier) close() error { + if err := c.getErr(); err != nil { + return err + } + + var err error + c.result, err = c.to.CommitBlockList(c.ctx, c.id.issued(), &blockblob.CommitBlockListOptions{ + Tags: c.o.Tags, + Metadata: c.o.Metadata, + Tier: c.o.AccessTier, + HTTPHeaders: c.o.HTTPHeaders, + CPKInfo: c.o.CPKInfo, + CPKScopeInfo: c.o.CPKScopeInfo, + AccessConditions: c.o.AccessConditions, + }) + return err +} + +// id allows the creation of unique IDs based on UUID4 + an int32. This auto-increments. +type id struct { + u [64]byte + num uint32 + all []string +} + +// newID constructs a new id. +func newID() *id { + uu := guuid.New() + u := [64]byte{} + copy(u[:], uu[:]) + return &id{u: u} +} + +// next returns the next ID. +func (id *id) next() string { + defer atomic.AddUint32(&id.num, 1) + + binary.BigEndian.PutUint32(id.u[len(guuid.UUID{}):], atomic.LoadUint32(&id.num)) + str := base64.StdEncoding.EncodeToString(id.u[:]) + id.all = append(id.all, str) + + return str +} + +// issued returns all ids that have been issued. This returned value shares the internal slice, so it is not safe to modify the return. +// The value is only valid until the next time next() is called. +func (id *id) issued() []string { + return id.all +} + +// Code taken from Azure SDK for go blockblob/chunkwriting.go + +// bufferManager provides an abstraction for the management of buffers. +// this is mostly for testing purposes, but does allow for different implementations without changing the algorithm. +type bufferManager[T ~[]byte] interface { + // Acquire returns the channel that contains the pool of buffers. + Acquire() <-chan T + + // Release releases the buffer back to the pool for reuse/cleanup. + Release(T) + + // Grow grows the number of buffers, up to the predefined max. + // It returns the total number of buffers or an error. + // No error is returned if the number of buffers has reached max. + // This is called only from the reading goroutine. + Grow() int + + // Free cleans up all buffers. + Free() +} + +// mmb is a memory mapped buffer +type mmb []byte + +// TODO (niro): consider implementation refactoring +// newMMB creates a new memory mapped buffer with the specified size +func newMMB(size int64) mmb { + return make(mmb, size) +} + +// delete cleans up the memory mapped buffer +func (m *mmb) delete() { +} + +// mmbPool implements the bufferManager interface. +// it uses anonymous memory mapped files for buffers. +// don't use this type directly, use newMMBPool() instead. +type mmbPool struct { + buffers chan mmb + count int + max int + size int64 +} + +func newMMBPool(maxBuffers int, bufferSize int64) bufferManager[mmb] { + return &mmbPool{ + buffers: make(chan mmb, maxBuffers), + max: maxBuffers, + size: bufferSize, + } +} + +func (pool *mmbPool) Acquire() <-chan mmb { + return pool.buffers +} + +func (pool *mmbPool) Grow() int { + if pool.count < pool.max { + buffer := newMMB(pool.size) + pool.buffers <- buffer + pool.count++ + } + return pool.count +} + +func (pool *mmbPool) Release(buffer mmb) { + pool.buffers <- buffer +} + +func (pool *mmbPool) Free() { + for i := 0; i < pool.count; i++ { + buffer := <-pool.buffers + buffer.delete() + } + pool.count = 0 +} diff --git a/block/azure/client_cache.go b/block/azure/client_cache.go new file mode 100644 index 00000000..c4f73f36 --- /dev/null +++ b/block/azure/client_cache.go @@ -0,0 +1,144 @@ +package azure + +import ( + "context" + "fmt" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/service" + lru "github.com/hnlq715/golang-lru" + "github.com/jiaozifs/jiaozifs/block/params" + "github.com/puzpuzpuz/xsync" +) + +const UDCCacheExpiry = time.Hour +const UDCCacheWorkaroundDivider = 2 + +type ClientCache struct { + serviceToClient *xsync.MapOf[string, *service.Client] + containerToClient *xsync.MapOf[string, *container.Client] + // udcCache - User Delegation Credential cache used to reduce POST requests while creating pre-signed URLs + udcCache *lru.ARCCache + params params.Azure +} + +func NewCache(p params.Azure) (*ClientCache, error) { + l, err := lru.NewARCWithExpire(udcCacheSize, UDCCacheExpiry/UDCCacheWorkaroundDivider) + // TODO(Guys): dividing the udc cache expiry by 2 is a workaround for the fact that this package does not handle expiry correctly, we can remove this once we use https://github.com/hashicorp/golang-lru expirables + if err != nil { + return nil, err + } + + return &ClientCache{ + serviceToClient: xsync.NewMapOf[*service.Client](), + containerToClient: xsync.NewMapOf[*container.Client](), + udcCache: l, + params: p, + }, nil +} + +func mapKey(storageAccount, containerName string) string { + return fmt.Sprintf("%s#%s", storageAccount, containerName) +} + +func (c *ClientCache) NewContainerClient(storageAccount, containerName string) (*container.Client, error) { + key := mapKey(storageAccount, containerName) + + var err error + cl, _ := c.containerToClient.LoadOrCompute(key, func() *container.Client { + var svc *service.Client + svc, err = c.NewServiceClient(storageAccount) + if err != nil { + return nil + } + return svc.NewContainerClient(containerName) + }) + if err != nil { + return nil, err + } + + return cl, nil +} + +func (c *ClientCache) NewServiceClient(storageAccount string) (*service.Client, error) { + p := c.params + // Use StorageAccessKey to initialize storage account client only if it was provided for this given storage account + // Otherwise fall back to the default credentials + if p.StorageAccount != storageAccount { + p.StorageAccount = storageAccount + p.StorageAccessKey = "" + } + + var err error + cl, _ := c.serviceToClient.LoadOrCompute(storageAccount, func() *service.Client { + var svc *service.Client + svc, err = BuildAzureServiceClient(p) + if err != nil { + return nil + } + return svc + }) + if err != nil { + return nil, err + } + + return cl, nil +} + +func (c *ClientCache) NewUDC(ctx context.Context, storageAccount string, expiry *time.Time) (*service.UserDelegationCredential, error) { + var udc *service.UserDelegationCredential + // Check udcCache + res, ok := c.udcCache.Get(storageAccount) + if !ok { + baseTime := time.Now().UTC().Add(-10 * time.Second) + // UDC expiry time of PreSignedExpiry + hour + udcExpiry := expiry.Add(UDCCacheExpiry) + info := service.KeyInfo{ + Start: to.Ptr(baseTime.UTC().Format(sas.TimeFormat)), + Expiry: to.Ptr(udcExpiry.Format(sas.TimeFormat)), + } + svc, err := c.NewServiceClient(storageAccount) + if err != nil { + return nil, err + } + udc, err = svc.GetUserDelegationCredential(ctx, info, nil) + if err != nil { + return nil, err + } + // UDC expires after PreSignedExpiry + hour but cache entry expires after an hour + c.udcCache.Add(storageAccount, udc) + } else { + udc = res.(*service.UserDelegationCredential) + } + return udc, nil +} + +func BuildAzureServiceClient(params params.Azure) (*service.Client, error) { + var url string + if params.TestEndpointURL != "" { // For testing purposes - override default url template + url = params.TestEndpointURL + } else { + url = fmt.Sprintf(BlobEndpointFormat, params.StorageAccount) + } + + options := service.ClientOptions{ClientOptions: azcore.ClientOptions{Retry: policy.RetryOptions{TryTimeout: params.TryTimeout}}} + if params.StorageAccessKey != "" { + cred, err := service.NewSharedKeyCredential(params.StorageAccount, params.StorageAccessKey) + if err != nil { + return nil, fmt.Errorf("invalid credentials: %w", err) + } + return service.NewClientWithSharedKeyCredential(url, cred, &options) + } + + defaultCreds, err := azidentity.NewDefaultAzureCredential(nil) + if err != nil { + return nil, fmt.Errorf("missing credentials: %w", err) + } + return service.NewClient(url, defaultCreds, &options) +} diff --git a/block/azure/client_test.go b/block/azure/client_test.go new file mode 100644 index 00000000..27bfa6b3 --- /dev/null +++ b/block/azure/client_test.go @@ -0,0 +1,57 @@ +package azure_test + +import ( + "net/url" + "testing" + + "github.com/jiaozifs/jiaozifs/block" + "github.com/jiaozifs/jiaozifs/block/azure" + "github.com/stretchr/testify/require" +) + +func TestExtraction(t *testing.T) { + parse := func(p string) *url.URL { + u, err := url.Parse(p) + require.NoError(t, err) + return u + } + tests := []struct { + name string + url *url.URL + expectedErr error + expectedStorageAccount string + }{ + { + name: "simple", + url: parse("https://somestorageaccount.blob.core.windows.net/newcontainer/2023/"), + expectedStorageAccount: "somestorageaccount", + }, + { + name: "no container", + url: parse("https://somestorageaccount.blob.core.windows.net/"), + expectedStorageAccount: "somestorageaccount", + }, + { + name: "long prefix", + url: parse("https://somestorageaccount.blob.core.windows.net/container/somestorageaccount.blob.core.windows.net"), + expectedStorageAccount: "somestorageaccount", + }, + { + name: "No subdomains", + url: parse("https://Rgeaccountblobcorewindowsnet/newcontainer/2023/"), + expectedErr: block.ErrInvalidAddress, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actualStorageAccount, err := azure.ExtractStorageAccount(tt.url) + if tt.expectedErr == nil { + require.NoError(t, err) + require.Equal(t, tt.expectedStorageAccount, actualStorageAccount) + } else { + require.ErrorIs(t, err, tt.expectedErr) + } + }) + } +} diff --git a/block/azure/main_test.go b/block/azure/main_test.go new file mode 100644 index 00000000..31543e37 --- /dev/null +++ b/block/azure/main_test.go @@ -0,0 +1,115 @@ +package azure_test + +import ( + "context" + "fmt" + "log" + "net" + "os" + "testing" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" + "github.com/benburkert/dns" + "github.com/ory/dockertest/v3" +) + +const ( + azuriteContainerTimeoutSeconds = 10 * 60 // 10 min + containerName = "container1" + accountName = "account1" + accountKey = "key1" + + domain = "azurite.test" // TLD for test + accountHost = accountName + "." + domain +) + +var ( + pool *dockertest.Pool + blockURL string +) + +func createDNSResolver(domain string) { + zone := dns.Zone{ + Origin: domain + ".", + TTL: 5 * time.Minute, + RRs: dns.RRSet{ + accountName: map[dns.Type][]dns.Record{ + dns.TypeA: { + &dns.A{A: net.IPv4(127, 0, 0, 1).To4()}, + }, + }, + }, + } + + mux := new(dns.ResolveMux) + mux.Handle(dns.TypeANY, zone.Origin, &zone) + client := &dns.Client{ + Resolver: mux, + } + net.DefaultResolver = &net.Resolver{ + PreferGo: true, + Dial: client.Dial, + } +} + +func runAzurite(dockerPool *dockertest.Pool) (string, func()) { + ctx := context.Background() + resource, err := dockerPool.Run("mcr.microsoft.com/azure-storage/azurite", "3.26.0", []string{ + fmt.Sprintf("AZURITE_ACCOUNTS=%s:%s", accountName, accountKey), + }) + if err != nil { + panic(err) + } + createDNSResolver(domain) + + // set cleanup + closer := func() { + err := dockerPool.Purge(resource) + if err != nil { + panic("could not purge Azurite container: " + err.Error()) + } + } + + // expire, just to make sure + err = resource.Expire(azuriteContainerTimeoutSeconds) + if err != nil { + panic("could not expire Azurite container: " + err.Error()) + } + + // create connection and test container + port := resource.GetPort("10000/tcp") + url := fmt.Sprintf("http://%s:%s", accountHost, port) + cred, err := azblob.NewSharedKeyCredential(accountName, accountKey) + if err != nil { + panic(err) + } + + blob, err := azblob.NewClientWithSharedKeyCredential(url, cred, nil) + if err != nil { + panic(err) + } + _, err = blob.CreateContainer(ctx, containerName, nil) + if err != nil { + fmt.Println(err) + panic(err) + } + + // return container URL + return url, closer +} + +func TestMain(m *testing.M) { + _ = os.Setenv("NO_PROXY", "*."+domain) //ignore proxy for domain do not remove it + + var err error + pool, err = dockertest.NewPool("") + if err != nil { + log.Fatalf("Could not connect to Docker: %s", err) + } + var cleanup func() + blockURL, cleanup = runAzurite(pool) + code := m.Run() + cleanup() + os.Exit(code) +} diff --git a/block/azure/multipart_block_writer.go b/block/azure/multipart_block_writer.go new file mode 100644 index 00000000..88cd4d9c --- /dev/null +++ b/block/azure/multipart_block_writer.go @@ -0,0 +1,225 @@ +package azure + +import ( + "bufio" + "context" + "encoding/base64" + "encoding/hex" + "fmt" + "io" + "sort" + "strconv" + "strings" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blockblob" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container" + "github.com/google/uuid" + logging "github.com/ipfs/go-log/v2" + "github.com/jiaozifs/jiaozifs/block" +) + +var log = logging.Logger("azure") + +type MultipartBlockWriter struct { + reader *block.HashingReader // the reader that would be passed to copyFromReader, this is needed in order to get size and md5 + // to is the location we are writing our chunks to. + to *blockblob.Client + toIDs *blockblob.Client + toSizes *blockblob.Client + etag string +} + +func NewMultipartBlockWriter(reader *block.HashingReader, containerURL container.Client, objName string) *MultipartBlockWriter { + return &MultipartBlockWriter{ + reader: reader, + to: containerURL.NewBlockBlobClient(objName), + toIDs: containerURL.NewBlockBlobClient(objName + idSuffix), + toSizes: containerURL.NewBlockBlobClient(objName + sizeSuffix), + } +} + +func (m *MultipartBlockWriter) StageBlock(ctx context.Context, base64BlockID string, body io.ReadSeekCloser, options *blockblob.StageBlockOptions) (blockblob.StageBlockResponse, error) { + return m.to.StageBlock(ctx, base64BlockID, body, options) +} + +func (m *MultipartBlockWriter) CommitBlockList(ctx context.Context, ids []string, options *blockblob.CommitBlockListOptions) (blockblob.CommitBlockListResponse, error) { + m.etag = "\"" + hex.EncodeToString(m.reader.Md5.Sum(nil)) + "\"" + base64Etag := base64.StdEncoding.EncodeToString([]byte(m.etag)) + + // write to blockIDs + pd := strings.Join(ids, "\n") + "\n" + var leaseAccessConditions *blob.LeaseAccessConditions + if options.AccessConditions != nil { + leaseAccessConditions = options.AccessConditions.LeaseAccessConditions + } + _, err := m.toIDs.StageBlock(ctx, base64Etag, streaming.NopCloser(strings.NewReader(pd)), &blockblob.StageBlockOptions{ + LeaseAccessConditions: leaseAccessConditions, + }) + if err != nil { + return blockblob.CommitBlockListResponse{}, fmt.Errorf("failed staging part data: %w", err) + } + // write block sizes + sd := strconv.Itoa(int(m.reader.CopiedSize)) + "\n" + _, err = m.toSizes.StageBlock(ctx, base64Etag, streaming.NopCloser(strings.NewReader(sd)), &blockblob.StageBlockOptions{ + LeaseAccessConditions: leaseAccessConditions, + }) + if err != nil { + return blockblob.CommitBlockListResponse{}, fmt.Errorf("failed staging part data: %w", err) + } + + return blockblob.CommitBlockListResponse{}, err +} + +func (m *MultipartBlockWriter) Upload(_ context.Context, _ io.ReadSeekCloser, _ *blockblob.UploadOptions) (blockblob.UploadResponse, error) { + panic("Should not be called") +} + +func completeMultipart(ctx context.Context, parts []block.MultipartPart, container container.Client, objName string) (*block.CompleteMultiPartUploadResponse, error) { + sort.Slice(parts, func(i, j int) bool { + return parts[i].PartNumber < parts[j].PartNumber + }) + // extract staging blockIDs + metaBlockIDs := make([]string, len(parts)) + for i, part := range parts { + // add Quotations marks (") if missing, Etags sent by spark include Quotations marks, Etags sent aws cli don't include Quotations marks + etag := strings.Trim(part.ETag, "\"") + etag = "\"" + etag + "\"" + base64Etag := base64.StdEncoding.EncodeToString([]byte(etag)) + metaBlockIDs[i] = base64Etag + } + + stageBlockIDs, err := getMultipartIDs(ctx, container, objName, metaBlockIDs) + if err != nil { + return nil, err + } + + size, err := getMultipartSize(ctx, container, objName, metaBlockIDs) + if err != nil { + return nil, err + } + blobURL := container.NewBlockBlobClient(objName) + + res, err := blobURL.CommitBlockList(ctx, stageBlockIDs, nil) + if err != nil { + return nil, err + } + etag := string(*res.ETag) + return &block.CompleteMultiPartUploadResponse{ + ETag: etag, + ContentLength: size, + }, nil +} + +func getMultipartIDs(ctx context.Context, container container.Client, objName string, base64BlockIDs []string) ([]string, error) { + blobURL := container.NewBlockBlobClient(objName + idSuffix) + _, err := blobURL.CommitBlockList(ctx, base64BlockIDs, nil) + if err != nil { + return nil, err + } + + downloadResponse, err := blobURL.DownloadStream(ctx, nil) + if err != nil { + return nil, err + } + bodyStream := downloadResponse.Body + defer func() { + _ = bodyStream.Close() + }() + scanner := bufio.NewScanner(bodyStream) + ids := make([]string, 0) + for scanner.Scan() { + id := scanner.Text() + ids = append(ids, id) + } + + // remove + _, err = blobURL.Delete(ctx, nil) + if err != nil { + log.With("blob_url", blobURL.URL()).Warnf("Failed to delete multipart ids data file %v", err) + } + return ids, nil +} + +func getMultipartSize(ctx context.Context, container container.Client, objName string, base64BlockIDs []string) (int64, error) { + blobURL := container.NewBlockBlobClient(objName + sizeSuffix) + _, err := blobURL.CommitBlockList(ctx, base64BlockIDs, nil) + if err != nil { + return 0, err + } + + downloadResponse, err := blobURL.DownloadStream(ctx, nil) + if err != nil { + return 0, err + } + bodyStream := downloadResponse.Body + defer func() { + _ = bodyStream.Close() + }() + scanner := bufio.NewScanner(bodyStream) + size := 0 + for scanner.Scan() { + s := scanner.Text() + stageSize, err := strconv.Atoi(s) + if err != nil { + return 0, err + } + size += stageSize + } + + // remove + _, err = blobURL.Delete(ctx, nil) + if err != nil { + log.With("blob_url", blobURL.URL()).Warnf("Failed to delete multipart size data file %v", err) + } + return int64(size), nil +} + +func copyPartRange(ctx context.Context, destinationContainer container.Client, destinationObjName string, sourceBlobURL blockblob.Client, startPosition, count int64) (*block.UploadPartResponse, error) { + base64BlockID := generateRandomBlockID() + _, err := sourceBlobURL.StageBlockFromURL(ctx, base64BlockID, sourceBlobURL.URL(), + &blockblob.StageBlockFromURLOptions{ + Range: blob.HTTPRange{ + Offset: startPosition, + Count: count, + }, + }) + if err != nil { + return nil, err + } + + // add size and id to etag + response, err := sourceBlobURL.GetProperties(ctx, nil) + if err != nil { + return nil, err + } + etag := "\"" + hex.EncodeToString(response.ContentMD5) + "\"" + size := response.ContentLength + base64Etag := base64.StdEncoding.EncodeToString([]byte(etag)) + // stage id data + blobIDsURL := destinationContainer.NewBlockBlobClient(destinationObjName + idSuffix) + _, err = blobIDsURL.StageBlock(ctx, base64Etag, streaming.NopCloser(strings.NewReader(base64BlockID+"\n")), nil) + if err != nil { + return nil, fmt.Errorf("failed staging part data: %w", err) + } + + // stage size data + sizeData := fmt.Sprintf("%d\n", size) + blobSizesURL := destinationContainer.NewBlockBlobClient(destinationObjName + sizeSuffix) + _, err = blobSizesURL.StageBlock(ctx, base64Etag, streaming.NopCloser(strings.NewReader(sizeData)), nil) + if err != nil { + return nil, fmt.Errorf("failed staging part data: %w", err) + } + + return &block.UploadPartResponse{ + ETag: strings.Trim(etag, `"`), + }, nil +} + +func generateRandomBlockID() string { + uu := uuid.New() + u := [64]byte{} + copy(u[:], uu[:]) + return base64.StdEncoding.EncodeToString(u[:]) +} diff --git a/block/azure/stats.go b/block/azure/stats.go new file mode 100644 index 00000000..def8f12c --- /dev/null +++ b/block/azure/stats.go @@ -0,0 +1,31 @@ +package azure + +import ( + "strconv" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +var durationHistograms = promauto.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "azure_operation_duration_seconds", + Help: "durations of outgoing azure operations", + }, + []string{"operation", "error"}) + +var requestSizeHistograms = promauto.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "azure_operation_size_bytes", + Help: "handled sizes of outgoing azure operations", + Buckets: prometheus.ExponentialBuckets(1, 10, 10), //nolint: gomnd + }, []string{"operation", "error"}) + +func reportMetrics(operation string, start time.Time, sizeBytes *int64, err *error) { + isErrStr := strconv.FormatBool(*err != nil) + durationHistograms.WithLabelValues(operation, isErrStr).Observe(time.Since(start).Seconds()) + if sizeBytes != nil { + requestSizeHistograms.WithLabelValues(operation, isErrStr).Observe(float64(*sizeBytes)) + } +} diff --git a/block/azure/walker.go b/block/azure/walker.go new file mode 100644 index 00000000..1cf577f1 --- /dev/null +++ b/block/azure/walker.go @@ -0,0 +1,257 @@ +package azure + +import ( + "context" + "encoding/hex" + "errors" + "fmt" + "net/url" + "strings" + + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/service" + "github.com/jiaozifs/jiaozifs/block" +) + +const DirectoryBlobMetadataKey = "hdi_isfolder" + +var ErrAzureInvalidURL = errors.New("invalid Azure storage URL") + +func NewAzureBlobWalker(svc *service.Client) (*BlobWalker, error) { + return &BlobWalker{ + client: svc, + mark: block.Mark{HasMore: true}, + }, nil +} + +type BlobWalker struct { + client *service.Client + mark block.Mark +} + +// extractAzurePrefix takes a URL that looks like this: https://storageaccount.blob.core.windows.net/container/prefix +// and return the URL for the container and a prefix, if one exists +func extractAzurePrefix(storageURI *url.URL) (*url.URL, string, error) { + path := strings.TrimLeft(storageURI.Path, "/") + if len(path) == 0 { + return nil, "", fmt.Errorf("%w: could not parse container URL: %s", ErrAzureInvalidURL, storageURI) + } + parts := strings.SplitN(path, "/", 2) // nolint: gomnd + if len(parts) == 1 { + // we only have a container + return storageURI, "", nil + } + // we have both prefix and storage container, rebuild URL + relativePath := url.URL{Path: "/" + parts[0]} + return storageURI.ResolveReference(&relativePath), parts[1], nil +} + +func getAzureBlobURL(containerURL *url.URL, blobName string) *url.URL { + relativePath := url.URL{Path: containerURL.Path + "/" + blobName} + return containerURL.ResolveReference(&relativePath) +} + +func (a *BlobWalker) Walk(ctx context.Context, storageURI *url.URL, op block.WalkOptions, walkFn func(e block.ObjectStoreEntry) error) error { + // we use bucket as container and prefix as path + containerURL, prefix, err := extractAzurePrefix(storageURI) + if err != nil { + return err + } + var basePath string + if idx := strings.LastIndex(prefix, "/"); idx != -1 { + basePath = prefix[:idx+1] + } + + qk, err := ResolveBlobURLInfoFromURL(containerURL) + if err != nil { + return err + } + + containerClient := a.client.NewContainerClient(qk.ContainerName) + listBlob := containerClient.NewListBlobsFlatPager(&azblob.ListBlobsFlatOptions{ + Prefix: &prefix, + Marker: &op.ContinuationToken, + Include: container.ListBlobsInclude{ + Metadata: true, + }, + }) + + for listBlob.More() { + resp, err := listBlob.NextPage(ctx) + if err != nil { + return err + } + if resp.Marker != nil { + a.mark.ContinuationToken = *resp.Marker + } + for _, blobInfo := range resp.Segment.BlobItems { + // skipping everything in the page which is before 'After' (without forgetting the possible empty string key!) + if op.After != "" && *blobInfo.Name <= op.After { + continue + } + + // Skip folders + if isBlobItemFolder(blobInfo) { + continue + } + + a.mark.LastKey = *blobInfo.Name + if err := walkFn(block.ObjectStoreEntry{ + FullKey: *blobInfo.Name, + RelativeKey: strings.TrimPrefix(*blobInfo.Name, basePath), + Address: getAzureBlobURL(containerURL, *blobInfo.Name).String(), + ETag: extractBlobItemEtag(blobInfo), + Mtime: *blobInfo.Properties.LastModified, + Size: *blobInfo.Properties.ContentLength, + }); err != nil { + return err + } + } + } + + a.mark = block.Mark{ + HasMore: false, + } + + return nil +} + +// isBlobItemFolder returns true if the blob item is a folder. +// Make sure that metadata is populated before calling this function. +// Example: for listing using blob API passing options with `Include: container.ListBlobsInclude{ Metadata: true }` +// will populate the metadata. +func isBlobItemFolder(blobItem *container.BlobItem) bool { + if blobItem.Metadata == nil { + return false + } + if blobItem.Properties.ContentLength != nil && *blobItem.Properties.ContentLength != 0 { + return false + } + isFolder, ok := blobItem.Metadata[DirectoryBlobMetadataKey] + if !ok || isFolder == nil { + return false + } + return *isFolder == "true" +} + +// extractBlobItemEtag etag set by content md5 with fallback to use Etag value +func extractBlobItemEtag(blobItem *container.BlobItem) string { + if blobItem.Properties.ContentMD5 != nil { + return hex.EncodeToString(blobItem.Properties.ContentMD5) + } + if blobItem.Properties.ETag != nil { + etag := string(*blobItem.Properties.ETag) + return strings.TrimFunc(etag, func(r rune) bool { return r == '"' || r == ' ' }) + } + return "" +} + +func (a *BlobWalker) Marker() block.Mark { + return a.mark +} + +func (a *BlobWalker) GetSkippedEntries() []block.ObjectStoreEntry { + return nil +} + +// +// DataLakeWalker +// + +func NewAzureDataLakeWalker(svc *service.Client, skipOutOfOrder bool) (*DataLakeWalker, error) { + return &DataLakeWalker{ + client: svc, + mark: block.Mark{HasMore: true}, + skipOutOfOrder: skipOutOfOrder, + }, nil +} + +type DataLakeWalker struct { + client *service.Client + mark block.Mark + skipped []block.ObjectStoreEntry + skipOutOfOrder bool +} + +func (a *DataLakeWalker) Walk(ctx context.Context, storageURI *url.URL, op block.WalkOptions, walkFn func(e block.ObjectStoreEntry) error) error { + // we use bucket as container and prefix as a path + containerURL, prefix, err := extractAzurePrefix(storageURI) + if err != nil { + return err + } + var basePath string + if idx := strings.LastIndex(prefix, "/"); idx != -1 { + basePath = prefix[:idx+1] + } + + qk, err := ResolveBlobURLInfoFromURL(containerURL) + if err != nil { + return err + } + + containerClient := a.client.NewContainerClient(qk.ContainerName) + listBlob := containerClient.NewListBlobsFlatPager(&azblob.ListBlobsFlatOptions{ + Prefix: &prefix, + Marker: &op.ContinuationToken, + Include: container.ListBlobsInclude{ + Metadata: true, + }, + }) + + skipCount := 0 + prev := "" + for listBlob.More() { + resp, err := listBlob.NextPage(ctx) + if err != nil { + return err + } + if resp.Marker != nil { + a.mark.ContinuationToken = *resp.Marker + } + for _, blobInfo := range resp.Segment.BlobItems { + // skipping everything in the page which is before 'After' (without forgetting the possible empty string key!) + if op.After != "" && *blobInfo.Name <= op.After { + continue + } + + // Skip folders + if isBlobItemFolder(blobInfo) { + continue + } + + entry := block.ObjectStoreEntry{ + FullKey: *blobInfo.Name, + RelativeKey: strings.TrimPrefix(*blobInfo.Name, basePath), + Address: getAzureBlobURL(containerURL, *blobInfo.Name).String(), + ETag: extractBlobItemEtag(blobInfo), + Mtime: *blobInfo.Properties.LastModified, + Size: *blobInfo.Properties.ContentLength, + } + if a.skipOutOfOrder && strings.Compare(prev, *blobInfo.Name) > 0 { // skip out of order + a.skipped = append(a.skipped, entry) + skipCount++ + continue + } + prev = *blobInfo.Name + + a.mark.LastKey = *blobInfo.Name + if err := walkFn(entry); err != nil { + return err + } + } + } + a.mark = block.Mark{ + HasMore: false, + } + + return nil +} + +func (a *DataLakeWalker) Marker() block.Mark { + return a.mark +} + +func (a *DataLakeWalker) GetSkippedEntries() []block.ObjectStoreEntry { + return a.skipped +} diff --git a/block/blocktest/adapter.go b/block/blocktest/adapter.go new file mode 100644 index 00000000..8140762f --- /dev/null +++ b/block/blocktest/adapter.go @@ -0,0 +1,415 @@ +package blocktest + +import ( + "bytes" + "context" + "fmt" + "io" + "net/url" + "path" + "path/filepath" + "sort" + "strings" + "testing" + + "github.com/go-test/deep" + "github.com/jiaozifs/jiaozifs/block" + "github.com/stretchr/testify/require" + "github.com/thanhpk/randstr" +) + +func AdapterTest(t *testing.T, adapter block.Adapter, storageNamespace, externalPath string) { + t.Run("Adapter_PutGet", func(t *testing.T) { testAdapterPutGet(t, adapter, storageNamespace, externalPath) }) + t.Run("Adapter_Copy", func(t *testing.T) { testAdapterCopy(t, adapter, storageNamespace) }) + t.Run("Adapter_Remove", func(t *testing.T) { testAdapterRemove(t, adapter, storageNamespace) }) + t.Run("Adapter_MultipartUpload", func(t *testing.T) { testAdapterMultipartUpload(t, adapter, storageNamespace) }) + t.Run("Adapter_Exists", func(t *testing.T) { testAdapterExists(t, adapter, storageNamespace) }) + t.Run("Adapter_GetRange", func(t *testing.T) { testAdapterGetRange(t, adapter, storageNamespace) }) + t.Run("Adapter_Walker", func(t *testing.T) { testAdapterWalker(t, adapter, storageNamespace) }) +} + +func testAdapterPutGet(t *testing.T, adapter block.Adapter, storageNamespace, externalPath string) { + ctx := context.Background() + const contents = "test_file" + size := int64(len(contents)) + + cases := []struct { + name string + identifierType block.IdentifierType + path string + }{ + {"identifier_relative", block.IdentifierTypeRelative, "test_file"}, + {"identifier_full", block.IdentifierTypeFull, externalPath + "/" + "test_file"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + obj := block.ObjectPointer{ + StorageNamespace: storageNamespace, + Identifier: c.path, + IdentifierType: c.identifierType, + } + + err := adapter.Put(ctx, obj, size, strings.NewReader(contents), block.PutOpts{}) + require.NoError(t, err) + + reader, err := adapter.Get(ctx, obj, size) + require.NoError(t, err) + defer func() { + require.NoError(t, reader.Close()) + }() + got, err := io.ReadAll(reader) + require.NoError(t, err) + require.Equal(t, contents, string(got)) + }) + } +} + +func testAdapterCopy(t *testing.T, adapter block.Adapter, storageNamespace string) { + ctx := context.Background() + contents := "foo bar baz quux" + src := block.ObjectPointer{ + StorageNamespace: storageNamespace, + Identifier: "src", + IdentifierType: block.IdentifierTypeRelative, + } + dst := block.ObjectPointer{ + StorageNamespace: storageNamespace, + Identifier: "export/to/dst", + IdentifierType: block.IdentifierTypeRelative, + } + + require.NoError(t, adapter.Put(ctx, src, int64(len(contents)), strings.NewReader(contents), block.PutOpts{})) + + require.NoError(t, adapter.Copy(ctx, src, dst)) + reader, err := adapter.Get(ctx, dst, 0) + require.NoError(t, err) + got, err := io.ReadAll(reader) + require.NoError(t, err) + require.Equal(t, contents, string(got)) +} + +func testAdapterRemove(t *testing.T, adapter block.Adapter, storageNamespace string) { + ctx := context.Background() + const content = "Content used for testing" + tests := []struct { + name string + additionalObjects []string + path string + wantErr bool + wantTree []string + }{ + { + name: "test_single", + path: "README", + wantErr: false, + wantTree: []string{}, + }, + + { + name: "test_under_folder", + path: "src/tools.go", + wantErr: false, + wantTree: []string{}, + }, + { + name: "test_under_multiple_folders", + path: "a/b/c/d.txt", + wantErr: false, + wantTree: []string{}, + }, + { + name: "file_in_the_way", + path: "a/b/c/d.txt", + additionalObjects: []string{"a/b/blocker.txt"}, + wantErr: false, + wantTree: []string{"/a/b/blocker.txt"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // setup env + envObjects := tt.additionalObjects + envObjects = append(envObjects, tt.path) + for _, p := range envObjects { + obj := block.ObjectPointer{ + StorageNamespace: storageNamespace, + Identifier: tt.name + "/" + p, + IdentifierType: block.IdentifierTypeRelative, + } + require.NoError(t, adapter.Put(ctx, obj, int64(len(content)), strings.NewReader(content), block.PutOpts{})) + } + + // test Remove + obj := block.ObjectPointer{ + StorageNamespace: storageNamespace, + Identifier: tt.name + "/" + tt.path, + IdentifierType: block.IdentifierTypeRelative, + } + if err := adapter.Remove(ctx, obj); (err != nil) != tt.wantErr { + t.Errorf("Remove() error = %v, wantErr %v", err, tt.wantErr) + } + + qk, err := adapter.ResolveNamespace(storageNamespace, tt.name, block.IdentifierTypeRelative) + require.NoError(t, err) + + tree := dumpPathTree(t, ctx, adapter, qk) + if diff := deep.Equal(tt.wantTree, tree); diff != nil { + t.Errorf("Remove() tree diff = %s", diff) + } + }) + } +} + +func dumpPathTree(t testing.TB, ctx context.Context, adapter block.Adapter, qk block.QualifiedKey) []string { //nolint + t.Helper() + tree := make([]string, 0) + + uri, err := url.Parse(qk.Format()) + require.NoError(t, err) + + w, err := adapter.GetWalker(uri) + require.NoError(t, err) + + walker := block.NewWrapper(w, uri) + require.NoError(t, err) + + err = walker.Walk(ctx, block.WalkOptions{}, func(e block.ObjectStoreEntry) error { + _, p, _ := strings.Cut(e.Address, uri.String()) + tree = append(tree, p) + return nil + }) + if err != nil { + t.Fatalf("walking on '%s': %s", uri.String(), err) + } + sort.Strings(tree) + return tree +} + +func createMultipartFile() ([][]byte, []byte) { + const ( + multipartNumberOfParts = 3 + multipartPartSize = 5 * 1024 * 1024 + ) + parts := make([][]byte, multipartNumberOfParts) + var partsConcat []byte + for i := 0; i < multipartNumberOfParts; i++ { + parts[i] = randstr.Bytes(multipartPartSize + i) + partsConcat = append(partsConcat, parts[i]...) + } + return parts, partsConcat +} + +func testAdapterMultipartUpload(t *testing.T, adapter block.Adapter, storageNamespace string) { + ctx := context.Background() + parts, full := createMultipartFile() + + cases := []struct { + name string + path string + }{ + {"simple", "abc"}, + {"nested", "foo/bar"}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + obj := block.ObjectPointer{ + StorageNamespace: storageNamespace, + Identifier: c.path, + IdentifierType: block.IdentifierTypeRelative, + } + resp, err := adapter.CreateMultiPartUpload(ctx, obj, nil, block.CreateMultiPartUploadOpts{}) + require.NoError(t, err) + + multiParts := make([]block.MultipartPart, len(parts)) + for i, content := range parts { + partNumber := i + 1 + partResp, err := adapter.UploadPart(ctx, obj, int64(len(content)), bytes.NewReader(content), resp.UploadID, partNumber) + require.NoError(t, err) + multiParts[i].PartNumber = partNumber + multiParts[i].ETag = partResp.ETag + } + _, err = adapter.CompleteMultiPartUpload(ctx, obj, resp.UploadID, &block.MultipartUploadCompletion{ + Part: multiParts, + }) + require.NoError(t, err) + + reader, err := adapter.Get(ctx, obj, 0) + require.NoError(t, err) + + got, err := io.ReadAll(reader) + require.NoError(t, err) + + require.Equal(t, full, got) + }) + } +} + +func testAdapterExists(t *testing.T, adapter block.Adapter, storageNamespace string) { + // TODO (niro): Test abs paths + const contents = "exists" + ctx := context.Background() + err := adapter.Put(ctx, block.ObjectPointer{ + StorageNamespace: storageNamespace, + Identifier: contents, + IdentifierType: block.IdentifierTypeRelative, + }, int64(len(contents)), strings.NewReader(contents), block.PutOpts{}) + require.NoError(t, err) + + err = adapter.Put(ctx, block.ObjectPointer{ + StorageNamespace: storageNamespace, + Identifier: "nested/and/" + contents, + IdentifierType: block.IdentifierTypeRelative, + }, int64(len(contents)), strings.NewReader(contents), block.PutOpts{}) + require.NoError(t, err) + + cases := []struct { + name string + path string + exists bool + }{ + {"exists", "exists", true}, + {"nested_exists", "nested/and/exists", true}, + {"simple_missing", "missing", false}, + {"nested_missing", "nested/down", false}, + {"nested_deep_missing", "nested/quite/deeply/and/missing", false}, + } + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + ok, err := adapter.Exists(ctx, block.ObjectPointer{ + StorageNamespace: storageNamespace, + Identifier: tt.path, + IdentifierType: block.IdentifierTypeRelative, + }) + require.NoError(t, err) + require.Equal(t, tt.exists, ok) + }) + } +} + +func testAdapterGetRange(t *testing.T, adapter block.Adapter, storageNamespace string) { + ctx := context.Background() + part1 := "this is the first part " + part2 := "this is the last part" + err := adapter.Put(ctx, block.ObjectPointer{ + StorageNamespace: storageNamespace, + Identifier: "test_file", + IdentifierType: block.IdentifierTypeRelative, + }, int64(len(part1+part2)), strings.NewReader(part1+part2), block.PutOpts{}) + require.NoError(t, err) + + cases := []struct { + name string + startPos int + endPos int + expected string + expectFailure bool + }{ + {"read_suffix", len(part1), len(part1 + part2), part2, false}, + {"read_prefix", 0, len(part1) - 1, part1, false}, + {"read_middle", 8, len(part1) + 6, "the first part this is", false}, + // {"end_smaller_than_start", 10, 1, "", false}, // TODO (niro): To be determined + // {"negative_position", -1, len(part1), "", true}, // S3 and Azure not aligned + {"one_byte", 1, 1, string(part1[1]), false}, + {"out_of_bounds", 0, len(part1+part2) + 10, part1 + part2, false}, + } + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + reader, err := adapter.GetRange(ctx, block.ObjectPointer{ + StorageNamespace: storageNamespace, + Identifier: "test_file", + IdentifierType: block.IdentifierTypeRelative, + }, int64(tt.startPos), int64(tt.endPos)) + require.Equal(t, tt.expectFailure, err != nil) + if err == nil { + got, err := io.ReadAll(reader) + require.NoError(t, err) + require.Equal(t, tt.expected, string(got)) + } + }) + } +} + +func testAdapterWalker(t *testing.T, adapter block.Adapter, storageNamespace string) { + ctx := context.Background() + const ( + testPrefix = "test_walker" + filesAndFolders = 5 + contents = "test_file" + ) + + for i := 0; i < filesAndFolders; i++ { + for j := 0; j < filesAndFolders; j++ { + err := adapter.Put(ctx, block.ObjectPointer{ + StorageNamespace: storageNamespace, + Identifier: fmt.Sprintf("%s/folder_%d/test_file_%d", testPrefix, filesAndFolders-i-1, filesAndFolders-j-1), + IdentifierType: block.IdentifierTypeRelative, + }, int64(len(contents)), strings.NewReader(contents), block.PutOpts{}) + require.NoError(t, err) + } + } + + err := adapter.Put(ctx, block.ObjectPointer{ + StorageNamespace: storageNamespace, + Identifier: fmt.Sprintf("%s/folder_0.txt", testPrefix), + IdentifierType: block.IdentifierTypeRelative, + }, int64(len(contents)), strings.NewReader(contents), block.PutOpts{}) + require.NoError(t, err) + + cases := []struct { + name string + prefix string + }{ + { + name: "root", + prefix: "", + }, + { + name: "prefix", + prefix: "folder_1", + }, + { + name: "prefix/", + prefix: "folder_2", + }, + } + for _, tt := range cases { + qk, err := adapter.ResolveNamespace(storageNamespace, filepath.Join(testPrefix, tt.prefix), block.IdentifierTypeRelative) + require.NoError(t, err) + uri, err := url.Parse(qk.Format()) + require.NoError(t, err) + t.Run(tt.name, func(t *testing.T) { + reader, err := adapter.GetWalker(uri) + require.NoError(t, err) + + var results []string + err = reader.Walk(ctx, uri, block.WalkOptions{}, func(e block.ObjectStoreEntry) error { + results = append(results, e.RelativeKey) + return nil + }) + require.NoError(t, err) + var prefix string + if tt.prefix == "" { + if adapter.BlockstoreType() != block.BlockstoreTypeLocal { + prefix = testPrefix + } + + require.Equal(t, path.Join(prefix, "folder_0.txt"), results[0]) + results = results[1:] + for i := 0; i < filesAndFolders; i++ { + for j := 0; j < filesAndFolders; j++ { + require.Equal(t, path.Join(prefix, fmt.Sprintf("folder_%d/test_file_%d", i, j)), results[i*filesAndFolders+j]) + } + } + } else { + if adapter.BlockstoreType() != block.BlockstoreTypeLocal { + prefix = tt.prefix + } + for j := 0; j < filesAndFolders; j++ { + require.Equal(t, path.Join(prefix, fmt.Sprintf("test_file_%d", j)), results[j]) + } + } + }) + } +} diff --git a/block/errors.go b/block/errors.go new file mode 100644 index 00000000..0ae3d157 --- /dev/null +++ b/block/errors.go @@ -0,0 +1,13 @@ +package block + +import "github.com/pkg/errors" + +var ( + ErrDataNotFound = errors.New("not found") + ErrOperationNotSupported = errors.New("operation not supported") + ErrAsyncCopyFailed = errors.New("async copy failed") + ErrBadIndex = errors.New("bad index") + ErrForbidden = errors.New("forbidden") + ErrInvalidAddress = errors.New("invalid address") + ErrInvalidNamespace = errors.New("invalid namespace") +) diff --git a/block/factory/build.go b/block/factory/build.go new file mode 100644 index 00000000..89bc1298 --- /dev/null +++ b/block/factory/build.go @@ -0,0 +1,140 @@ +package factory + +import ( + "context" + "fmt" + + "cloud.google.com/go/storage" + "github.com/aws/aws-sdk-go-v2/service/s3" + logging "github.com/ipfs/go-log/v2" + "github.com/jiaozifs/jiaozifs/block" + "github.com/jiaozifs/jiaozifs/block/azure" + "github.com/jiaozifs/jiaozifs/block/gs" + "github.com/jiaozifs/jiaozifs/block/local" + "github.com/jiaozifs/jiaozifs/block/mem" + "github.com/jiaozifs/jiaozifs/block/params" + s3a "github.com/jiaozifs/jiaozifs/block/s3" + "github.com/jiaozifs/jiaozifs/block/transient" + "golang.org/x/oauth2/google" + "google.golang.org/api/option" +) + +var log = logging.Logger("block_factory") + +const ( + // googleAuthCloudPlatform - Cloud Storage authentication https://cloud.google.com/storage/docs/authentication + googleAuthCloudPlatform = "https://www.googleapis.com/auth/cloud-platform" +) + +func BuildBlockAdapter(ctx context.Context, c params.AdapterConfig) (block.Adapter, error) { + blockstore := c.BlockstoreType() + log.With("type", blockstore). + Info("initialize blockstore adapter") + switch blockstore { + case block.BlockstoreTypeLocal: + p, err := c.BlockstoreLocalParams() + if err != nil { + return nil, err + } + return buildLocalAdapter(ctx, p) + case block.BlockstoreTypeS3: + p, err := c.BlockstoreS3Params() + if err != nil { + return nil, err + } + return buildS3Adapter(ctx, p) + case block.BlockstoreTypeMem, "memory": + return mem.New(ctx), nil + case block.BlockstoreTypeTransient: + return transient.New(ctx), nil + case block.BlockstoreTypeGS: + p, err := c.BlockstoreGSParams() + if err != nil { + return nil, err + } + return buildGSAdapter(ctx, p) + case block.BlockstoreTypeAzure: + p, err := c.BlockstoreAzureParams() + if err != nil { + return nil, err + } + return azure.NewAdapter(ctx, p) + default: + return nil, fmt.Errorf("%w '%s' please choose one of %s", + block.ErrInvalidAddress, blockstore, []string{block.BlockstoreTypeLocal, block.BlockstoreTypeS3, block.BlockstoreTypeAzure, block.BlockstoreTypeMem, block.BlockstoreTypeTransient, block.BlockstoreTypeGS}) + } +} + +func buildLocalAdapter(_ context.Context, params params.Local) (*local.Adapter, error) { + adapter, err := local.NewAdapter(params.Path, + local.WithAllowedExternalPrefixes(params.AllowedExternalPrefixes), + local.WithImportEnabled(params.ImportEnabled), + ) + if err != nil { + return nil, fmt.Errorf("got error opening a local block adapter with path %s: %w", params.Path, err) + } + log.With( + "type", "local", + "path", params.Path, + ).Info("initialized blockstore adapter") + return adapter, nil +} + +func BuildS3Client(ctx context.Context, params params.S3) (*s3.Client, error) { + cfg, err := s3a.LoadConfig(ctx, params) + if err != nil { + return nil, err + } + + client := s3.NewFromConfig(cfg, s3a.WithClientParams(params)) + return client, nil +} + +func buildS3Adapter(ctx context.Context, params params.S3) (*s3a.Adapter, error) { + opts := []s3a.AdapterOption{ + s3a.WithDiscoverBucketRegion(params.DiscoverBucketRegion), + s3a.WithPreSignedExpiry(params.PreSignedExpiry), + s3a.WithDisablePreSigned(params.DisablePreSigned), + s3a.WithDisablePreSignedUI(params.DisablePreSignedUI), + } + if params.ServerSideEncryption != "" { + opts = append(opts, s3a.WithServerSideEncryption(params.ServerSideEncryption)) + } + if params.ServerSideEncryptionKmsKeyID != "" { + opts = append(opts, s3a.WithServerSideEncryptionKmsKeyID(params.ServerSideEncryptionKmsKeyID)) + } + adapter, err := s3a.NewAdapter(ctx, params, opts...) + if err != nil { + return nil, err + } + log.With("type", "s3").Info("initialized blockstore adapter") + return adapter, nil +} + +func BuildGSClient(ctx context.Context, params params.GS) (*storage.Client, error) { + var opts []option.ClientOption + if params.CredentialsFile != "" { + opts = append(opts, option.WithCredentialsFile(params.CredentialsFile)) + } else if params.CredentialsJSON != "" { + cred, err := google.CredentialsFromJSON(ctx, []byte(params.CredentialsJSON), googleAuthCloudPlatform) + if err != nil { + return nil, err + } + opts = append(opts, option.WithCredentials(cred)) + } + return storage.NewClient(ctx, opts...) +} + +func buildGSAdapter(ctx context.Context, params params.GS) (*gs.Adapter, error) { + client, err := BuildGSClient(ctx, params) + if err != nil { + return nil, err + } + adapter := gs.NewAdapter(client, + gs.WithPreSignedExpiry(params.PreSignedExpiry), + gs.WithDisablePreSigned(params.DisablePreSigned), + gs.WithDisablePreSignedUI(params.DisablePreSignedUI), + ) + log.With("type", "gs").Info("initialized blockstore adapter") + return adapter, nil +} diff --git a/block/gs/adapter.go b/block/gs/adapter.go new file mode 100644 index 00000000..d19ebecc --- /dev/null +++ b/block/gs/adapter.go @@ -0,0 +1,584 @@ +package gs + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "sort" + "strings" + "time" + + "cloud.google.com/go/storage" + logging "github.com/ipfs/go-log/v2" + "github.com/jiaozifs/jiaozifs/block" + "google.golang.org/api/iterator" +) + +var log = logging.Logger("gs") + +const ( + MaxMultipartObjects = 10000 + + delimiter = "/" + partSuffix = ".part_" + markerSuffix = ".multipart" +) + +var ( + ErrMismatchPartETag = errors.New("mismatch part ETag") + ErrMismatchPartName = errors.New("mismatch part name") + ErrMaxMultipartObjects = errors.New("maximum multipart object reached") + ErrPartListMismatch = errors.New("multipart part list mismatch") + ErrMissingTargetAttrs = errors.New("missing target attributes") +) + +type Adapter struct { + client *storage.Client + preSignedExpiry time.Duration + disablePreSigned bool + disablePreSignedUI bool +} + +func WithPreSignedExpiry(v time.Duration) func(a *Adapter) { + return func(a *Adapter) { + if v == 0 { + a.preSignedExpiry = block.DefaultPreSignExpiryDuration + } else { + a.preSignedExpiry = v + } + } +} + +func WithDisablePreSigned(b bool) func(a *Adapter) { + return func(a *Adapter) { + if b { + a.disablePreSigned = true + } + } +} + +func WithDisablePreSignedUI(b bool) func(a *Adapter) { + return func(a *Adapter) { + if b { + a.disablePreSignedUI = true + } + } +} + +func NewAdapter(client *storage.Client, opts ...func(adapter *Adapter)) *Adapter { + a := &Adapter{ + client: client, + preSignedExpiry: block.DefaultPreSignExpiryDuration, + } + for _, opt := range opts { + opt(a) + } + return a +} + +func (a *Adapter) newPreSignedTime() time.Time { + return time.Now().UTC().Add(a.preSignedExpiry) +} + +func (a *Adapter) Put(ctx context.Context, obj block.ObjectPointer, sizeBytes int64, reader io.Reader, _ block.PutOpts) error { + var err error + defer reportMetrics("Put", time.Now(), &sizeBytes, &err) + bucket, key, err := a.extractParamsFromObj(obj) + if err != nil { + return err + } + w := a.client.Bucket(bucket).Object(key).NewWriter(ctx) + _, err = io.Copy(w, reader) + if err != nil { + return fmt.Errorf("io.Copy: %w", err) + } + err = w.Close() + if err != nil { + return fmt.Errorf("writer.Close: %w", err) + } + return nil +} + +func (a *Adapter) Get(ctx context.Context, obj block.ObjectPointer, _ int64) (io.ReadCloser, error) { + var err error + defer reportMetrics("Get", time.Now(), nil, &err) + bucket, key, err := a.extractParamsFromObj(obj) + if err != nil { + return nil, err + } + r, err := a.client.Bucket(bucket).Object(key).NewReader(ctx) + if isErrNotFound(err) { + return nil, block.ErrDataNotFound + } + if err != nil { + log.Errorf("failed to get object bucket %s key %s %v", bucket, key, err) + return nil, err + } + return r, nil +} + +func (a *Adapter) GetWalker(uri *url.URL) (block.Walker, error) { + if err := block.ValidateStorageType(uri, block.StorageTypeGS); err != nil { + return nil, err + } + return NewGCSWalker(a.client), nil +} + +func (a *Adapter) GetPreSignedURL(_ context.Context, obj block.ObjectPointer, mode block.PreSignMode) (string, time.Time, error) { + if a.disablePreSigned { + return "", time.Time{}, block.ErrOperationNotSupported + } + + var err error + defer reportMetrics("GetPreSignedURL", time.Now(), nil, &err) + + bucket, key, err := a.extractParamsFromObj(obj) + if err != nil { + return "", time.Time{}, err + } + method := http.MethodGet + if mode == block.PreSignModeWrite { + method = http.MethodPut + } + opts := &storage.SignedURLOptions{ + Scheme: storage.SigningSchemeV4, + Method: method, + Expires: a.newPreSignedTime(), + } + k, err := a.client.Bucket(bucket).SignedURL(key, opts) + if err != nil { + log.Errorf("error generating pre-signed URL %v", err) + return "", time.Time{}, err + } + // TODO(#6347): Report expiry. + return k, time.Time{}, nil +} + +func isErrNotFound(err error) bool { + return errors.Is(err, storage.ErrObjectNotExist) +} + +func (a *Adapter) Exists(ctx context.Context, obj block.ObjectPointer) (bool, error) { + var err error + defer reportMetrics("Exists", time.Now(), nil, &err) + bucket, key, err := a.extractParamsFromObj(obj) + if err != nil { + return false, err + } + _, err = a.client.Bucket(bucket).Object(key).Attrs(ctx) + if isErrNotFound(err) { + return false, nil + } + if err != nil { + return false, err + } + return true, nil +} + +func (a *Adapter) GetRange(ctx context.Context, obj block.ObjectPointer, startPosition int64, endPosition int64) (io.ReadCloser, error) { + var err error + defer reportMetrics("GetRange", time.Now(), nil, &err) + bucket, key, err := a.extractParamsFromObj(obj) + if err != nil { + return nil, err + } + r, err := a.client.Bucket(bucket).Object(key).NewRangeReader(ctx, startPosition, endPosition-startPosition+1) + if isErrNotFound(err) { + return nil, block.ErrDataNotFound + } + if err != nil { + log.Errorf("failed to get object bucket %s key %s %v", bucket, key, err) + return nil, err + } + return r, nil +} + +func (a *Adapter) GetProperties(ctx context.Context, obj block.ObjectPointer) (block.Properties, error) { + var err error + defer reportMetrics("GetProperties", time.Now(), nil, &err) + var props block.Properties + bucket, key, err := a.extractParamsFromObj(obj) + if err != nil { + return props, err + } + _, err = a.client.Bucket(bucket).Object(key).Attrs(ctx) + if err != nil { + return props, err + } + return props, nil +} + +func (a *Adapter) Remove(ctx context.Context, obj block.ObjectPointer) error { + var err error + defer reportMetrics("Remove", time.Now(), nil, &err) + bucket, key, err := a.extractParamsFromObj(obj) + if err != nil { + return err + } + err = a.client.Bucket(bucket).Object(key).Delete(ctx) + if err != nil { + return fmt.Errorf("Object(%q).Delete: %w", key, err) + } + return nil +} + +func (a *Adapter) Copy(ctx context.Context, sourceObj, destinationObj block.ObjectPointer) error { + var err error + defer reportMetrics("Copy", time.Now(), nil, &err) + dstBucket, dstKey, err := a.extractParamsFromObj(destinationObj) + if err != nil { + return fmt.Errorf("resolve destination: %w", err) + } + srcBucket, srcKey, err := a.extractParamsFromObj(sourceObj) + if err != nil { + return fmt.Errorf("resolve source: %w", err) + } + destinationObjectHandle := a.client.Bucket(dstBucket).Object(dstKey) + sourceObjectHandle := a.client.Bucket(srcBucket).Object(srcKey) + _, err = destinationObjectHandle.CopierFrom(sourceObjectHandle).Run(ctx) + if err != nil { + return fmt.Errorf("copy: %w", err) + } + return nil +} + +func (a *Adapter) CreateMultiPartUpload(ctx context.Context, obj block.ObjectPointer, _ *http.Request, _ block.CreateMultiPartUploadOpts) (*block.CreateMultiPartUploadResponse, error) { + var err error + defer reportMetrics("CreateMultiPartUpload", time.Now(), nil, &err) + bucket, uploadID, err := a.extractParamsFromObj(obj) + if err != nil { + return nil, err + } + // we keep a marker file to identify multipart in progress + objName := formatMultipartMarkerFilename(uploadID) + o := a.client.Bucket(bucket).Object(objName) + w := o.NewWriter(ctx) + _, err = io.WriteString(w, uploadID) + if err != nil { + return nil, fmt.Errorf("io.WriteString: %w", err) + } + err = w.Close() + if err != nil { + return nil, fmt.Errorf("writer.Close: %w", err) + } + // log information + log.With( + "upload_id", uploadID, + "qualified_ns", bucket, + "qualified_key", uploadID, + "key", obj.Identifier, + ).Debug("created multipart upload") + return &block.CreateMultiPartUploadResponse{ + UploadID: uploadID, + }, nil +} + +func (a *Adapter) UploadPart(ctx context.Context, obj block.ObjectPointer, sizeBytes int64, reader io.Reader, uploadID string, partNumber int) (*block.UploadPartResponse, error) { + var err error + defer reportMetrics("UploadPart", time.Now(), &sizeBytes, &err) + bucket, _, err := a.extractParamsFromObj(obj) + if err != nil { + return nil, err + } + objName := formatMultipartFilename(uploadID, partNumber) + o := a.client.Bucket(bucket).Object(objName) + w := o.NewWriter(ctx) + _, err = io.Copy(w, reader) + if err != nil { + return nil, fmt.Errorf("io.Copy: %w", err) + } + err = w.Close() + if err != nil { + return nil, fmt.Errorf("writer.Close: %w", err) + } + attrs, err := o.Attrs(ctx) + if err != nil { + return nil, fmt.Errorf("object.Attrs: %w", err) + } + return &block.UploadPartResponse{ + ETag: attrs.Etag, + }, nil +} + +func (a *Adapter) UploadCopyPart(ctx context.Context, sourceObj, destinationObj block.ObjectPointer, uploadID string, partNumber int) (*block.UploadPartResponse, error) { + var err error + defer reportMetrics("UploadCopyPart", time.Now(), nil, &err) + bucket, _, err := a.extractParamsFromObj(destinationObj) + if err != nil { + return nil, err + } + objName := formatMultipartFilename(uploadID, partNumber) + o := a.client.Bucket(bucket).Object(objName) + + srcBucket, srcKey, err := a.extractParamsFromObj(sourceObj) + if err != nil { + return nil, fmt.Errorf("resolve source: %w", err) + } + sourceObjectHandle := a.client.Bucket(srcBucket).Object(srcKey) + + attrs, err := o.CopierFrom(sourceObjectHandle).Run(ctx) + if err != nil { + return nil, fmt.Errorf("CopierFrom: %w", err) + } + return &block.UploadPartResponse{ + ETag: attrs.Etag, + }, nil +} + +func (a *Adapter) UploadCopyPartRange(ctx context.Context, sourceObj, destinationObj block.ObjectPointer, uploadID string, partNumber int, startPosition, endPosition int64) (*block.UploadPartResponse, error) { + var err error + defer reportMetrics("UploadCopyPartRange", time.Now(), nil, &err) + bucket, _, err := a.extractParamsFromObj(destinationObj) + if err != nil { + return nil, err + } + objName := formatMultipartFilename(uploadID, partNumber) + o := a.client.Bucket(bucket).Object(objName) + + reader, err := a.GetRange(ctx, sourceObj, startPosition, endPosition) + if err != nil { + return nil, fmt.Errorf("GetRange: %w", err) + } + w := o.NewWriter(ctx) + _, err = io.Copy(w, reader) + if err != nil { + return nil, fmt.Errorf("copy: %w", err) + } + err = w.Close() + if err != nil { + _ = reader.Close() + return nil, fmt.Errorf("WriterClose: %w", err) + } + err = reader.Close() + if err != nil { + return nil, fmt.Errorf("ReaderClose: %w", err) + } + + attrs, err := o.Attrs(ctx) + if err != nil { + return nil, fmt.Errorf("object.Attrs: %w", err) + } + return &block.UploadPartResponse{ + ETag: attrs.Etag, + }, nil +} + +func (a *Adapter) AbortMultiPartUpload(ctx context.Context, obj block.ObjectPointer, uploadID string) error { + var err error + defer reportMetrics("AbortMultiPartUpload", time.Now(), nil, &err) + bucketName, _, err := a.extractParamsFromObj(obj) + if err != nil { + return err + } + bucket := a.client.Bucket(bucketName) + + // delete all related files by listing the prefix + it := bucket.Objects(ctx, &storage.Query{ + Prefix: uploadID, + Delimiter: delimiter, + }) + for { + attrs, err := it.Next() + if errors.Is(err, iterator.Done) { + break + } + if err != nil { + return fmt.Errorf("bucket(%s).Objects(): %w", bucketName, err) + } + if err := bucket.Object(attrs.Name).Delete(ctx); err != nil { + return fmt.Errorf("bucket(%s).object(%s).Delete(): %w", bucketName, attrs.Name, err) + } + } + return nil +} + +func (a *Adapter) CompleteMultiPartUpload(ctx context.Context, obj block.ObjectPointer, uploadID string, multipartList *block.MultipartUploadCompletion) (*block.CompleteMultiPartUploadResponse, error) { + var err error + defer reportMetrics("CompleteMultiPartUpload", time.Now(), nil, &err) + bucketName, key, err := a.extractParamsFromObj(obj) + if err != nil { + return nil, err + } + + lg := log.With( + "upload_id", uploadID, + "qualified_ns", bucketName, + "qualified_key", key, + "key", obj.Identifier, + ) + + // list bucket parts and validate request match + bucketParts, err := a.listMultipartUploadParts(ctx, bucketName, uploadID) + if err != nil { + return nil, err + } + // validate bucketParts match the request multipartList + err = a.validateMultipartUploadParts(uploadID, multipartList, bucketParts) + if err != nil { + return nil, err + } + + // prepare names + parts := make([]string, len(bucketParts)) + for i, part := range bucketParts { + parts[i] = part.Name + } + + // compose target object + targetAttrs, err := a.composeMultipartUploadParts(ctx, bucketName, uploadID, parts) + if err != nil { + lg.Errorf("CompleteMultipartUpload failed %v", err) + return nil, err + } + + // delete marker + bucket := a.client.Bucket(bucketName) + objMarker := bucket.Object(formatMultipartMarkerFilename(uploadID)) + if err := objMarker.Delete(ctx); err != nil { + lg.Warnf("Failed to delete multipart upload marker %v", err) + } + lg.Debug("completed multipart upload") + return &block.CompleteMultiPartUploadResponse{ + ETag: targetAttrs.Etag, + ContentLength: targetAttrs.Size, + }, nil +} + +func (a *Adapter) validateMultipartUploadParts(uploadID string, multipartList *block.MultipartUploadCompletion, bucketParts []*storage.ObjectAttrs) error { + if len(multipartList.Part) != len(bucketParts) { + return ErrPartListMismatch + } + for i, p := range multipartList.Part { + objName := formatMultipartFilename(uploadID, p.PartNumber) + if objName != bucketParts[i].Name { + return fmt.Errorf("invalid part at position %d: %w", i, ErrMismatchPartName) + } + if p.ETag != bucketParts[i].Etag { + return fmt.Errorf("invalid part at position %d: %w", i, ErrMismatchPartETag) + } + } + return nil +} + +func (a *Adapter) listMultipartUploadParts(ctx context.Context, bucketName string, uploadID string) ([]*storage.ObjectAttrs, error) { + bucket := a.client.Bucket(bucketName) + var bucketParts []*storage.ObjectAttrs + it := bucket.Objects(ctx, &storage.Query{ + Delimiter: delimiter, + Prefix: uploadID + partSuffix, + }) + for { + attrs, err := it.Next() + if errors.Is(err, iterator.Done) { + break + } + if err != nil { + return nil, fmt.Errorf("listing bucket '%s' upload '%s': %w", bucketName, uploadID, err) + } + bucketParts = append(bucketParts, attrs) + if len(bucketParts) > MaxMultipartObjects { + return nil, fmt.Errorf("listing bucket '%s' upload '%s': %w", bucketName, uploadID, ErrMaxMultipartObjects) + } + } + // sort by name - assume natual sort order + sort.Slice(bucketParts, func(i, j int) bool { + return bucketParts[i].Name < bucketParts[j].Name + }) + return bucketParts, nil +} + +func (a *Adapter) composeMultipartUploadParts(ctx context.Context, bucketName string, uploadID string, parts []string) (*storage.ObjectAttrs, error) { + // compose target from all parts + bucket := a.client.Bucket(bucketName) + var targetAttrs *storage.ObjectAttrs + err := ComposeAll(uploadID, parts, func(target string, parts []string) error { + objs := make([]*storage.ObjectHandle, len(parts)) + for i := range parts { + objs[i] = bucket.Object(parts[i]) + } + // compose target from parts + attrs, err := bucket.Object(target).ComposerFrom(objs...).Run(ctx) + if err != nil { + return err + } + if target == uploadID { + targetAttrs = attrs + } + // delete parts + for _, o := range objs { + if err := o.Delete(ctx); err != nil { + log.With( + "bucket", bucketName, + "parts", parts, + ).Warnf("Failed to delete multipart upload part while compose %v", err) + } + } + return nil + }) + if err == nil && targetAttrs == nil { + return nil, ErrMissingTargetAttrs + } + if err != nil { + return nil, err + } + return targetAttrs, nil +} + +func (a *Adapter) Close() error { + return a.client.Close() +} + +func (a *Adapter) BlockstoreType() string { + return block.BlockstoreTypeGS +} + +func (a *Adapter) GetStorageNamespaceInfo() block.StorageNamespaceInfo { + info := block.DefaultStorageNamespaceInfo(block.BlockstoreTypeGS) + if a.disablePreSigned { + info.PreSignSupport = false + } + if !(a.disablePreSignedUI || a.disablePreSigned) { + info.PreSignSupportUI = true + } + return info +} + +func (a *Adapter) extractParamsFromObj(obj block.ObjectPointer) (string, string, error) { + qk, err := a.ResolveNamespace(obj.StorageNamespace, obj.Identifier, obj.IdentifierType) + if err != nil { + return "", "", err + } + bucket, prefix, _ := strings.Cut(qk.GetStorageNamespace(), "/") + key := qk.GetKey() + if len(prefix) > 0 { // Avoid situations where prefix is empty or "/" + key = prefix + "/" + key + } + return bucket, key, nil +} + +func (a *Adapter) ResolveNamespace(storageNamespace, key string, identifierType block.IdentifierType) (block.QualifiedKey, error) { + qualifiedKey, err := block.DefaultResolveNamespace(storageNamespace, key, identifierType) + if err != nil { + return qualifiedKey, err + } + if qualifiedKey.GetStorageType() != block.StorageTypeGS { + return qualifiedKey, fmt.Errorf("expected storage type gs: %w", block.ErrInvalidAddress) + } + return qualifiedKey, nil +} + +func (a *Adapter) RuntimeStats() map[string]string { + return nil +} + +func formatMultipartFilename(uploadID string, partNumber int) string { + // keep natural sort order with zero padding + return fmt.Sprintf("%s"+partSuffix+"%05d", uploadID, partNumber) +} + +func formatMultipartMarkerFilename(uploadID string) string { + return uploadID + markerSuffix +} diff --git a/block/gs/adapter_test.go b/block/gs/adapter_test.go new file mode 100644 index 00000000..a73ac35c --- /dev/null +++ b/block/gs/adapter_test.go @@ -0,0 +1,78 @@ +package gs_test + +import ( + "net/url" + "regexp" + "testing" + + "github.com/jiaozifs/jiaozifs/block/blocktest" + "github.com/jiaozifs/jiaozifs/block/gs" + "github.com/stretchr/testify/require" +) + +func newAdapter() *gs.Adapter { + return gs.NewAdapter(client) +} + +func TestAdapter(t *testing.T) { + basePath, err := url.JoinPath("gs://", bucketName) + require.NoError(t, err) + localPath, err := url.JoinPath(basePath, "lakefs") + require.NoError(t, err) + externalPath, err := url.JoinPath(basePath, "external") + require.NoError(t, err) + + adapter := newAdapter() + defer func() { + require.NoError(t, adapter.Close()) + }() + + blocktest.AdapterTest(t, adapter, localPath, externalPath) +} + +func TestAdapterNamespace(t *testing.T) { + adapter := newAdapter() + defer func() { + require.NoError(t, adapter.Close()) + }() + + expr, err := regexp.Compile(adapter.GetStorageNamespaceInfo().ValidityRegex) + require.NoError(t, err) + + tests := []struct { + Name string + Namespace string + Success bool + }{ + { + Name: "valid_path", + Namespace: "gs://bucket/path/to/repo1", + Success: true, + }, + { + Name: "double_slash", + Namespace: "gs://bucket/path//to/repo1", + Success: true, + }, + { + Name: "invalid_schema", + Namespace: "s3:/test/adls/core/windows/net", + Success: false, + }, + { + Name: "invalid_path", + Namespace: "https://test/adls/core/windows/net", + Success: false, + }, + { + Name: "invalid_string", + Namespace: "this is a bad string", + Success: false, + }, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + require.Equal(t, tt.Success, expr.MatchString(tt.Namespace)) + }) + } +} diff --git a/block/gs/compose.go b/block/gs/compose.go new file mode 100644 index 00000000..33d6d744 --- /dev/null +++ b/block/gs/compose.go @@ -0,0 +1,33 @@ +package gs + +import ( + "fmt" +) + +const MaxPartsInCompose = 32 + +type ComposeFunc func(target string, parts []string) error + +func ComposeAll(target string, parts []string, composeFunc ComposeFunc) error { + for layer := 1; len(parts) > MaxPartsInCompose; layer++ { + var nextParts []string + for i := 0; i < len(parts); i += MaxPartsInCompose { + chunkSize := len(parts) - i + if chunkSize > MaxPartsInCompose { + chunkSize = MaxPartsInCompose + } + chunk := parts[i : i+chunkSize] + if chunkSize == 1 || (chunkSize < MaxPartsInCompose && len(nextParts)+chunkSize <= MaxPartsInCompose) { + nextParts = append(nextParts, chunk...) + } else { + targetName := fmt.Sprintf("%s_%d", chunk[0], layer) + if err := composeFunc(targetName, chunk); err != nil { + return err + } + nextParts = append(nextParts, targetName) + } + } + parts = nextParts + } + return composeFunc(target, parts) +} diff --git a/block/gs/compose_test.go b/block/gs/compose_test.go new file mode 100644 index 00000000..ebb816f7 --- /dev/null +++ b/block/gs/compose_test.go @@ -0,0 +1,51 @@ +package gs + +import ( + "fmt" + "strconv" + "testing" +) + +func TestComposeAll(t *testing.T) { + const targetFile = "data.file" + numberOfPartsTests := []int{1, 10, 10000} + for _, numberOfParts := range numberOfPartsTests { + t.Run("compose_"+strconv.Itoa(numberOfParts), func(t *testing.T) { + // prepare data + parts := make([]string, numberOfParts) + for i := 0; i < numberOfParts; i++ { + parts[i] = fmt.Sprintf("part%d", i) + } + + // map to track + usedParts := make(map[string]struct{}) + usedTargets := make(map[string]struct{}) + + // compose + err := ComposeAll(targetFile, parts, func(target string, parts []string) error { + for _, part := range parts { + if _, found := usedParts[part]; found { + t.Errorf("Part '%s' already composed", part) + } + usedParts[part] = struct{}{} + } + if _, found := usedTargets[target]; found { + t.Errorf("Target '%s' already composed with %s", target, parts) + } + usedTargets[target] = struct{}{} + return nil + }) + if err != nil { + t.Fatal(err) + } + for _, part := range parts { + if _, ok := usedParts[part]; !ok { + t.Error("Missing part:", part) + } + } + if _, ok := usedTargets[targetFile]; !ok { + t.Error("Missing target:", targetFile) + } + }) + } +} diff --git a/block/gs/main_test.go b/block/gs/main_test.go new file mode 100644 index 00000000..7242b1d3 --- /dev/null +++ b/block/gs/main_test.go @@ -0,0 +1,85 @@ +package gs_test + +import ( + "context" + "fmt" + "log" + "os" + "testing" + + "cloud.google.com/go/storage" + "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" + "google.golang.org/api/option" +) + +const bucketName = "bucket1" + +var client *storage.Client + +func TestMain(m *testing.M) { + const ( + emulatorContainerTimeoutSeconds = 10 * 60 // 10 min + emulatorTestEndpoint = "127.0.0.1" + emulatorTestPort = "4443" + gcsProjectID = "testProject" + ) + + ctx := context.Background() + // External port required for '-public-host' configuration in docker cmd + endpoint := fmt.Sprintf("%s:%s", emulatorTestEndpoint, emulatorTestPort) + pool, err := dockertest.NewPool("") + if err != nil { + log.Fatalf("Could not connect to Docker: %s", err) + } + resource, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "fsouza/fake-gcs-server", + Tag: "1.47.4", + Cmd: []string{ + "-scheme", + "http", + "-backend", + "memory", + "-public-host", + endpoint, + }, + ExposedPorts: []string{emulatorTestPort}, + PortBindings: map[docker.Port][]docker.PortBinding{ + docker.Port(fmt.Sprintf("%s/tcp", emulatorTestPort)): { + {HostIP: emulatorTestPort, HostPort: fmt.Sprintf("%s/tcp", emulatorTestPort)}, + }, + }, + }) + if err != nil { + log.Fatalf("Could not start fake-gcs-server: %s", err) + } + + // set cleanup + closer := func() { + err = pool.Purge(resource) + if err != nil { + log.Fatalf("Could not purge fake-gcs-server: %s", err) + } + } + + // expire, just to make sure + err = resource.Expire(emulatorContainerTimeoutSeconds) + if err != nil { + log.Fatalf("Could not expire fake-gcs-server: %s", err) + } + + // Create the test client and bucket + blockURL := fmt.Sprintf("http://%s/storage/v1/", endpoint) + client, err = storage.NewClient(ctx, option.WithEndpoint(blockURL), option.WithoutAuthentication()) + if err != nil { + log.Fatalf("Could not create gs client: %s", err) + } + + if err := client.Bucket(bucketName).Create(ctx, gcsProjectID, nil); err != nil { + log.Fatalf("Could not create bucket '%s': %s", bucketName, err) + } + + code := m.Run() + closer() + os.Exit(code) +} diff --git a/block/gs/stats.go b/block/gs/stats.go new file mode 100644 index 00000000..e6bb4080 --- /dev/null +++ b/block/gs/stats.go @@ -0,0 +1,31 @@ +package gs + +import ( + "strconv" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +var durationHistograms = promauto.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "gs_operation_duration_seconds", + Help: "durations of outgoing gs operations", + }, + []string{"operation", "error"}) + +var requestSizeHistograms = promauto.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "gs_operation_size_bytes", + Help: "handled sizes of outgoing gs operations", + Buckets: prometheus.ExponentialBuckets(1, 10, 10), //nolint: gomnd + }, []string{"operation", "error"}) + +func reportMetrics(operation string, start time.Time, sizeBytes *int64, err *error) { + isErrStr := strconv.FormatBool(*err != nil) + durationHistograms.WithLabelValues(operation, isErrStr).Observe(time.Since(start).Seconds()) + if sizeBytes != nil { + requestSizeHistograms.WithLabelValues(operation, isErrStr).Observe(float64(*sizeBytes)) + } +} diff --git a/block/gs/walker.go b/block/gs/walker.go new file mode 100644 index 00000000..9be907e4 --- /dev/null +++ b/block/gs/walker.go @@ -0,0 +1,81 @@ +package gs + +import ( + "context" + "encoding/hex" + "errors" + "fmt" + "net/url" + "strings" + + "cloud.google.com/go/storage" + "github.com/jiaozifs/jiaozifs/block" + "google.golang.org/api/iterator" +) + +type GCSWalker struct { + client *storage.Client + mark block.Mark +} + +func NewGCSWalker(client *storage.Client) *GCSWalker { + return &GCSWalker{client: client} +} + +func (w *GCSWalker) Walk(ctx context.Context, storageURI *url.URL, op block.WalkOptions, walkFn func(e block.ObjectStoreEntry) error) error { + prefix := strings.TrimLeft(storageURI.Path, "/") + var basePath string + if idx := strings.LastIndex(prefix, "/"); idx != -1 { + basePath = prefix[:idx+1] + } + iter := w.client. + Bucket(storageURI.Host). + Objects(ctx, &storage.Query{ + Prefix: prefix, + StartOffset: op.After, + }) + + for { + attrs, err := iter.Next() + if errors.Is(err, iterator.Done) { + break + } + if err != nil { + return fmt.Errorf("error listing objects at storage uri %s: %w", storageURI, err) + } + + // skipping first key (without forgetting the possible empty string key!) + if op.After != "" && attrs.Name <= op.After { + continue + } + + w.mark = block.Mark{ + LastKey: attrs.Name, + HasMore: true, + } + if err := walkFn(block.ObjectStoreEntry{ + FullKey: attrs.Name, + RelativeKey: strings.TrimPrefix(attrs.Name, basePath), + Address: fmt.Sprintf("gs://%s/%s", attrs.Bucket, attrs.Name), + ETag: hex.EncodeToString(attrs.MD5), + Mtime: attrs.Updated, + Size: attrs.Size, + }); err != nil { + return err + } + } + w.mark = block.Mark{ + LastKey: "", + HasMore: false, + } + + return nil +} + +func (w *GCSWalker) Marker() block.Mark { + return w.mark +} + +func (w *GCSWalker) GetSkippedEntries() []block.ObjectStoreEntry { + return nil +} diff --git a/block/hashing_reader.go b/block/hashing_reader.go new file mode 100644 index 00000000..8907d8d2 --- /dev/null +++ b/block/hashing_reader.go @@ -0,0 +1,57 @@ +package block + +import ( + "crypto/md5" //nolint:gosec + "crypto/sha256" + "hash" + "io" + "strconv" +) + +const ( + HashFunctionMD5 = iota + HashFunctionSHA256 +) + +type HashingReader struct { + Md5 hash.Hash + Sha256 hash.Hash + originalReader io.Reader + CopiedSize int64 +} + +func (s *HashingReader) Read(p []byte) (int, error) { + nb, err := s.originalReader.Read(p) + s.CopiedSize += int64(nb) + if s.Md5 != nil { + if _, err2 := s.Md5.Write(p[0:nb]); err2 != nil { + return nb, err2 + } + } + if s.Sha256 != nil { + if _, err2 := s.Sha256.Write(p[0:nb]); err2 != nil { + return nb, err2 + } + } + return nb, err +} + +func NewHashingReader(body io.Reader, hashTypes ...int) *HashingReader { + s := new(HashingReader) + s.originalReader = body + for hashType := range hashTypes { + switch hashType { + case HashFunctionMD5: + if s.Md5 == nil { + s.Md5 = md5.New() //nolint:gosec + } + case HashFunctionSHA256: + if s.Sha256 == nil { + s.Sha256 = sha256.New() + } + default: + panic("wrong hash type number " + strconv.Itoa(hashType)) + } + } + return s +} diff --git a/block/local/adapter.go b/block/local/adapter.go new file mode 100644 index 00000000..9d02c90c --- /dev/null +++ b/block/local/adapter.go @@ -0,0 +1,583 @@ +package local + +import ( + "context" + "crypto/md5" //nolint:gosec + "encoding/hex" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "os" + "path" + "path/filepath" + "sort" + "strconv" + "strings" + "time" + + "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/block" + "github.com/jiaozifs/jiaozifs/block/params" + "golang.org/x/exp/slices" +) + +const DefaultNamespacePrefix = block.BlockstoreTypeLocal + "://" + +type Adapter struct { + path string + removeEmptyDir bool + allowedExternalPrefixes []string + importEnabled bool +} + +var ( + ErrPathNotWritable = errors.New("path provided is not writable") + ErrInvalidUploadIDFormat = errors.New("invalid upload id format") + ErrBadPath = errors.New("bad path traversal blocked") +) + +type QualifiedKey struct { + block.CommonQualifiedKey + path string +} + +func (qk QualifiedKey) Format() string { + p := path.Join(qk.path, qk.GetStorageNamespace(), qk.GetKey()) + return qk.GetStorageType().Scheme() + "://" + p +} + +func (qk QualifiedKey) GetStorageType() block.StorageType { + return qk.CommonQualifiedKey.GetStorageType() +} + +func (qk QualifiedKey) GetStorageNamespace() string { + return qk.CommonQualifiedKey.GetStorageNamespace() +} + +func (qk QualifiedKey) GetKey() string { + return qk.CommonQualifiedKey.GetKey() +} + +func WithAllowedExternalPrefixes(prefixes []string) func(a *Adapter) { + return func(a *Adapter) { + a.allowedExternalPrefixes = prefixes + } +} + +func WithImportEnabled(b bool) func(a *Adapter) { + return func(a *Adapter) { + a.importEnabled = b + } +} + +func WithRemoveEmptyDir(b bool) func(a *Adapter) { + return func(a *Adapter) { + a.removeEmptyDir = b + } +} + +func NewAdapter(path string, opts ...func(a *Adapter)) (*Adapter, error) { + // Clean() the path so that misconfiguration does not allow path traversal. + path = filepath.Clean(path) + err := os.MkdirAll(path, 0o700) //nolint: gomnd + if err != nil { + return nil, err + } + if !isDirectoryWritable(path) { + return nil, ErrPathNotWritable + } + localAdapter := &Adapter{ + path: path, + removeEmptyDir: true, + } + for _, opt := range opts { + opt(localAdapter) + } + return localAdapter, nil +} + +func (l *Adapter) GetPreSignedURL(_ context.Context, _ block.ObjectPointer, _ block.PreSignMode) (string, time.Time, error) { + return "", time.Time{}, fmt.Errorf("local adapter presigned URL: %w", block.ErrOperationNotSupported) +} + +// verifyRelPath ensures that p is under the directory controlled by this adapter. It does not +// examine the filesystem and can mistakenly error out when symbolic links are involved. +func (l *Adapter) verifyRelPath(p string) error { + if !strings.HasPrefix(filepath.Clean(p), l.path) { + return fmt.Errorf("%s: %w", p, ErrBadPath) + } + return nil +} + +func (l *Adapter) extractParamsFromObj(ptr block.ObjectPointer) (string, error) { + if strings.HasPrefix(ptr.Identifier, DefaultNamespacePrefix) { + // check abs path + p := ptr.Identifier[len(DefaultNamespacePrefix):] + if err := VerifyAbsPath(p, l.path, l.allowedExternalPrefixes); err != nil { + return "", err + } + return p, nil + } + // relative path + if !strings.HasPrefix(ptr.StorageNamespace, DefaultNamespacePrefix) { + return "", fmt.Errorf("%w: storage namespace", ErrBadPath) + } + p := path.Join(l.path, ptr.StorageNamespace[len(DefaultNamespacePrefix):], ptr.Identifier) + if err := l.verifyRelPath(p); err != nil { + return "", err + } + return p, nil +} + +// maybeMkdir verifies path is allowed and runs f(path), but if f fails due to file-not-found +// MkdirAll's its dir and then runs it again. +func (l *Adapter) maybeMkdir(path string, f func(p string) (*os.File, error)) (*os.File, error) { + if err := l.verifyRelPath(path); err != nil { + return nil, err + } + ret, err := f(path) + if !errors.Is(err, os.ErrNotExist) { + return ret, err + } + d := filepath.Dir(filepath.Clean(path)) + if err = os.MkdirAll(d, 0o750); err != nil { //nolint: gomnd + return nil, err + } + return f(path) +} + +func (l *Adapter) Path() string { + return l.path +} + +func (l *Adapter) Put(_ context.Context, obj block.ObjectPointer, _ int64, reader io.Reader, _ block.PutOpts) error { + p, err := l.extractParamsFromObj(obj) + if err != nil { + return err + } + p = filepath.Clean(p) + f, err := l.maybeMkdir(p, os.Create) + if err != nil { + return err + } + defer func() { + _ = f.Close() + }() + _, err = io.Copy(f, reader) + return err +} + +func (l *Adapter) Remove(_ context.Context, obj block.ObjectPointer) error { + p, err := l.extractParamsFromObj(obj) + if err != nil { + return err + } + p = filepath.Clean(p) + err = os.Remove(p) + if err != nil { + return err + } + if l.removeEmptyDir { + dir := filepath.Dir(p) + repoRoot := obj.StorageNamespace[len(DefaultNamespacePrefix):] + removeEmptyDirUntil(dir, path.Join(l.path, repoRoot)) + } + return nil +} + +func removeEmptyDirUntil(dir string, stopAt string) { + if stopAt == "" { + return + } + if !strings.HasSuffix(stopAt, "/") { + stopAt += "/" + } + for strings.HasPrefix(dir, stopAt) && dir != stopAt { + err := os.Remove(dir) + if err != nil { + break + } + dir = filepath.Dir(dir) + if dir == "/" { + break + } + } +} + +func (l *Adapter) Copy(_ context.Context, sourceObj, destinationObj block.ObjectPointer) error { + source, err := l.extractParamsFromObj(sourceObj) + if err != nil { + return err + } + sourceFile, err := os.Open(filepath.Clean(source)) + defer func() { + _ = sourceFile.Close() + }() + if err != nil { + return err + } + dest, err := l.extractParamsFromObj(destinationObj) + if err != nil { + return err + } + destinationFile, err := l.maybeMkdir(dest, os.Create) + if err != nil { + return err + } + defer func() { + _ = destinationFile.Close() + }() + _, err = io.Copy(destinationFile, sourceFile) + return err +} + +func (l *Adapter) UploadCopyPart(ctx context.Context, sourceObj, destinationObj block.ObjectPointer, uploadID string, partNumber int) (*block.UploadPartResponse, error) { + if err := isValidUploadID(uploadID); err != nil { + return nil, err + } + r, err := l.Get(ctx, sourceObj, 0) + if err != nil { + return nil, err + } + md5Read := block.NewHashingReader(r, block.HashFunctionMD5) + fName := uploadID + fmt.Sprintf("-%05d", partNumber) + err = l.Put(ctx, block.ObjectPointer{StorageNamespace: destinationObj.StorageNamespace, Identifier: fName}, -1, md5Read, block.PutOpts{}) + if err != nil { + return nil, err + } + etag := hex.EncodeToString(md5Read.Md5.Sum(nil)) + return &block.UploadPartResponse{ + ETag: etag, + }, nil +} + +func (l *Adapter) UploadCopyPartRange(ctx context.Context, sourceObj, destinationObj block.ObjectPointer, uploadID string, partNumber int, startPosition, endPosition int64) (*block.UploadPartResponse, error) { + if err := isValidUploadID(uploadID); err != nil { + return nil, err + } + r, err := l.GetRange(ctx, sourceObj, startPosition, endPosition) + if err != nil { + return nil, err + } + md5Read := block.NewHashingReader(r, block.HashFunctionMD5) + fName := uploadID + fmt.Sprintf("-%05d", partNumber) + err = l.Put(ctx, block.ObjectPointer{StorageNamespace: destinationObj.StorageNamespace, Identifier: fName}, -1, md5Read, block.PutOpts{}) + if err != nil { + return nil, err + } + etag := hex.EncodeToString(md5Read.Md5.Sum(nil)) + return &block.UploadPartResponse{ + ETag: etag, + }, err +} + +func (l *Adapter) Get(_ context.Context, obj block.ObjectPointer, _ int64) (reader io.ReadCloser, err error) { + p, err := l.extractParamsFromObj(obj) + if err != nil { + return nil, err + } + f, err := os.OpenFile(filepath.Clean(p), os.O_RDONLY, 0o600) //nolint: gomnd + if os.IsNotExist(err) { + return nil, block.ErrDataNotFound + } + if err != nil { + return nil, err + } + return f, nil +} + +func (l *Adapter) GetWalker(uri *url.URL) (block.Walker, error) { + if err := block.ValidateStorageType(uri, block.StorageTypeLocal); err != nil { + return nil, err + } + + err := VerifyAbsPath(uri.Path, l.path, l.allowedExternalPrefixes) + if err != nil { + return nil, err + } + return NewLocalWalker(params.Local{ + Path: l.path, + ImportEnabled: l.importEnabled, + AllowedExternalPrefixes: l.allowedExternalPrefixes, + }), nil +} + +func (l *Adapter) Exists(_ context.Context, obj block.ObjectPointer) (bool, error) { + p, err := l.extractParamsFromObj(obj) + if err != nil { + return false, err + } + _, err = os.Stat(p) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } + return true, nil +} + +func (l *Adapter) GetRange(_ context.Context, obj block.ObjectPointer, start int64, end int64) (io.ReadCloser, error) { + if start < 0 || end < start { + return nil, block.ErrBadIndex + } + p, err := l.extractParamsFromObj(obj) + if err != nil { + return nil, err + } + f, err := os.Open(filepath.Clean(p)) + if err != nil { + if os.IsNotExist(err) { + return nil, block.ErrDataNotFound + } + return nil, err + } + return &struct { + io.Reader + io.Closer + }{ + Reader: io.NewSectionReader(f, start, end-start+1), + Closer: f, + }, nil +} + +func (l *Adapter) GetProperties(_ context.Context, obj block.ObjectPointer) (block.Properties, error) { + p, err := l.extractParamsFromObj(obj) + if err != nil { + return block.Properties{}, err + } + _, err = os.Stat(p) + if err != nil { + return block.Properties{}, err + } + // No properties, just return that it exists + return block.Properties{}, nil +} + +// isDirectoryWritable tests that pth, which must not be controllable by user input, is a +// writable directory. As there is no simple way to test this in windows, I prefer the "brute +// force" method of creating s dummy file. Will work in any OS. speed is not an issue, as +// this will be activated very few times during startup. +func isDirectoryWritable(pth string) bool { + f, err := os.CreateTemp(pth, "dummy") + if err != nil { + return false + } + _ = f.Close() + _ = os.Remove(f.Name()) + return true +} + +func (l *Adapter) CreateMultiPartUpload(_ context.Context, obj block.ObjectPointer, _ *http.Request, _ block.CreateMultiPartUploadOpts) (*block.CreateMultiPartUploadResponse, error) { + if strings.Contains(obj.Identifier, "/") { + fullPath, err := l.extractParamsFromObj(obj) + if err != nil { + return nil, err + } + fullDir := path.Dir(fullPath) + err = os.MkdirAll(fullDir, 0o750) //nolint: gomnd + if err != nil { + return nil, err + } + } + uidBytes := uuid.New() + uploadID := hex.EncodeToString(uidBytes[:]) + return &block.CreateMultiPartUploadResponse{ + UploadID: uploadID, + }, nil +} + +func (l *Adapter) UploadPart(ctx context.Context, obj block.ObjectPointer, _ int64, reader io.Reader, uploadID string, partNumber int) (*block.UploadPartResponse, error) { + if err := isValidUploadID(uploadID); err != nil { + return nil, err + } + md5Read := block.NewHashingReader(reader, block.HashFunctionMD5) + fName := uploadID + fmt.Sprintf("-%05d", partNumber) + err := l.Put(ctx, block.ObjectPointer{StorageNamespace: obj.StorageNamespace, Identifier: fName}, -1, md5Read, block.PutOpts{}) + etag := hex.EncodeToString(md5Read.Md5.Sum(nil)) + return &block.UploadPartResponse{ + ETag: etag, + }, err +} + +func (l *Adapter) AbortMultiPartUpload(_ context.Context, obj block.ObjectPointer, uploadID string) error { + if err := isValidUploadID(uploadID); err != nil { + return err + } + files, err := l.getPartFiles(uploadID, obj) + if err != nil { + return err + } + if err = l.removePartFiles(files); err != nil { + return err + } + return nil +} + +func (l *Adapter) CompleteMultiPartUpload(_ context.Context, obj block.ObjectPointer, uploadID string, multipartList *block.MultipartUploadCompletion) (*block.CompleteMultiPartUploadResponse, error) { + if err := isValidUploadID(uploadID); err != nil { + return nil, err + } + etag := computeETag(multipartList.Part) + "-" + strconv.Itoa(len(multipartList.Part)) + partFiles, err := l.getPartFiles(uploadID, obj) + if err != nil { + return nil, fmt.Errorf("part files not found for %s: %w", uploadID, err) + } + size, err := l.unitePartFiles(obj, partFiles) + if err != nil { + return nil, fmt.Errorf("multipart upload unite for %s: %w", uploadID, err) + } + if err = l.removePartFiles(partFiles); err != nil { + return nil, err + } + return &block.CompleteMultiPartUploadResponse{ + ETag: etag, + ContentLength: size, + }, nil +} + +func computeETag(parts []block.MultipartPart) string { + var etagHex []string + for _, p := range parts { + e := strings.Trim(p.ETag, `"`) + etagHex = append(etagHex, e) + } + s := strings.Join(etagHex, "") + b, _ := hex.DecodeString(s) + md5res := md5.Sum(b) //nolint:gosec + csm := hex.EncodeToString(md5res[:]) + return csm +} + +func (l *Adapter) unitePartFiles(identifier block.ObjectPointer, filenames []string) (int64, error) { + p, err := l.extractParamsFromObj(identifier) + if err != nil { + return 0, err + } + unitedFile, err := os.Create(p) + if err != nil { + return 0, fmt.Errorf("create path %s: %w", p, err) + } + files := make([]*os.File, 0, len(filenames)) + defer func() { + _ = unitedFile.Close() + for _, f := range files { + _ = f.Close() + } + }() + for _, name := range filenames { + if err := l.verifyRelPath(name); err != nil { + return 0, err + } + f, err := os.Open(filepath.Clean(name)) + if err != nil { + return 0, fmt.Errorf("open file %s: %w", name, err) + } + files = append(files, f) + } + // convert slice file files to readers + readers := make([]io.Reader, len(files)) + for i := range files { + readers[i] = files[i] + } + unitedReader := io.MultiReader(readers...) + return io.Copy(unitedFile, unitedReader) +} + +func (l *Adapter) removePartFiles(files []string) error { + var firstErr error + for _, name := range files { + if err := l.verifyRelPath(name); err != nil { + if firstErr == nil { + firstErr = err + } + } + // If removal fails prefer to skip the error: "only" wasted space. + _ = os.Remove(name) + } + return firstErr +} + +func (l *Adapter) getPartFiles(uploadID string, obj block.ObjectPointer) ([]string, error) { + newObj := block.ObjectPointer{ + StorageNamespace: obj.StorageNamespace, + Identifier: uploadID, + } + globPathPattern, err := l.extractParamsFromObj(newObj) + if err != nil { + return nil, err + } + globPathPattern += "*" + names, err := filepath.Glob(globPathPattern) + if err != nil { + return nil, err + } + sort.Strings(names) + return names, nil +} + +func (l *Adapter) BlockstoreType() string { + return block.BlockstoreTypeLocal +} + +func (l *Adapter) GetStorageNamespaceInfo() block.StorageNamespaceInfo { + info := block.DefaultStorageNamespaceInfo(block.BlockstoreTypeLocal) + info.PreSignSupport = false + info.DefaultNamespacePrefix = DefaultNamespacePrefix + info.ImportSupport = l.importEnabled + return info +} + +func (l *Adapter) ResolveNamespace(storageNamespace, key string, identifierType block.IdentifierType) (block.QualifiedKey, error) { + qk, err := block.DefaultResolveNamespace(storageNamespace, key, identifierType) + if err != nil { + return nil, err + } + + // Check if path allowed and return error if path is not allowed + _, err = l.extractParamsFromObj(block.ObjectPointer{ + StorageNamespace: storageNamespace, + Identifier: key, + IdentifierType: identifierType, + }) + if err != nil { + return nil, err + } + + return QualifiedKey{ + CommonQualifiedKey: qk, + path: l.path, + }, nil +} + +func (l *Adapter) RuntimeStats() map[string]string { + return nil +} + +func VerifyAbsPath(absPath, adapterPath string, allowedPrefixes []string) error { + // check we have a valid abs path + if !path.IsAbs(absPath) || path.Clean(absPath) != absPath { + return ErrBadPath + } + // point to storage namespace + if strings.HasPrefix(absPath, adapterPath) { + return nil + } + // allowed places + if !slices.ContainsFunc(allowedPrefixes, func(prefix string) bool { + return strings.HasPrefix(absPath, prefix) + }) { + return block.ErrForbidden + } + return nil +} + +func isValidUploadID(uploadID string) error { + _, err := hex.DecodeString(uploadID) + if err != nil { + return fmt.Errorf("%w: %s", ErrInvalidUploadIDFormat, err) + } + return nil +} diff --git a/block/local/adapter_test.go b/block/local/adapter_test.go new file mode 100644 index 00000000..a193ddcc --- /dev/null +++ b/block/local/adapter_test.go @@ -0,0 +1,66 @@ +package local_test + +import ( + "path" + "regexp" + "testing" + + "github.com/jiaozifs/jiaozifs/block" + "github.com/jiaozifs/jiaozifs/block/blocktest" + "github.com/jiaozifs/jiaozifs/block/local" + "github.com/stretchr/testify/require" +) + +const testStorageNamespace = "local://test" + +func TestLocalAdapter(t *testing.T) { + tmpDir := t.TempDir() + localPath := path.Join(tmpDir, "lakefs") + externalPath := block.BlockstoreTypeLocal + "://" + path.Join(tmpDir, "lakefs", "external") + adapter, err := local.NewAdapter(localPath, local.WithRemoveEmptyDir(false)) + if err != nil { + t.Fatal("Failed to create new adapter", err) + } + blocktest.AdapterTest(t, adapter, testStorageNamespace, externalPath) +} + +func TestAdapterNamespace(t *testing.T) { + tmpDir := t.TempDir() + localPath := path.Join(tmpDir, "lakefs") + adapter, err := local.NewAdapter(localPath, local.WithRemoveEmptyDir(false)) + require.NoError(t, err, "create new adapter") + expr, err := regexp.Compile(adapter.GetStorageNamespaceInfo().ValidityRegex) + require.NoError(t, err) + + tests := []struct { + Name string + Namespace string + Success bool + }{ + { + Name: "valid_path", + Namespace: "local://test/path/to/repo1", + Success: true, + }, + { + Name: "invalid_path", + Namespace: "~/test/path/to/repo1", + Success: false, + }, + { + Name: "s3", + Namespace: "s3://test/adls/core/windows/net", + Success: false, + }, + { + Name: "invalid_string", + Namespace: "this is a bad string", + Success: false, + }, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + require.Equal(t, tt.Success, expr.MatchString(tt.Namespace)) + }) + } +} diff --git a/block/local/etag_test.go b/block/local/etag_test.go new file mode 100644 index 00000000..84612d2e --- /dev/null +++ b/block/local/etag_test.go @@ -0,0 +1,27 @@ +package local + +import ( + "encoding/hex" + "testing" + + "github.com/jiaozifs/jiaozifs/block" +) + +const PartsNo = 30 + +func TestEtag(t *testing.T) { + var base [16]byte + b := base[:] + parts := make([]block.MultipartPart, PartsNo) + for i := 0; i < PartsNo; i++ { + for j := 0; j < len(b); j++ { + b[j] = byte(32 + i + j) + } + parts[i].PartNumber = i + 1 + parts[i].ETag = hex.EncodeToString(b) + } + etag := computeETag(parts) + if etag != "9cae1a3b7e97542c261cf2e1b50ba482" { + t.Fatalf("ETag value '%s' not as expected", etag) + } +} diff --git a/block/local/walker.go b/block/local/walker.go new file mode 100644 index 00000000..85b9ab89 --- /dev/null +++ b/block/local/walker.go @@ -0,0 +1,191 @@ +package local + +import ( + "context" + "crypto/md5" //nolint:gosec + "encoding/hex" + "encoding/json" + "io" + "io/fs" + "net/url" + "os" + "path" + "path/filepath" + "sort" + "strings" + + "github.com/jiaozifs/jiaozifs/block" + "github.com/jiaozifs/jiaozifs/block/params" + gonanoid "github.com/matoous/go-nanoid/v2" +) + +const cacheDirName = "_lakefs_cache" + +type Walker struct { + mark block.Mark + importHidden bool + allowedPrefixes []string + cacheLocation string + path string +} + +func NewLocalWalker(params params.Local) *Walker { + // without Path, we do not keep cache - will make walker very slow + var cacheLocation string + if params.Path != "" { + cacheLocation = filepath.Join(params.Path, cacheDirName) + } + return &Walker{ + mark: block.Mark{HasMore: true}, + importHidden: params.ImportHidden, + allowedPrefixes: params.AllowedExternalPrefixes, + cacheLocation: cacheLocation, + path: params.Path, + } +} + +func (l *Walker) Walk(_ context.Context, storageURI *url.URL, options block.WalkOptions, walkFn func(e block.ObjectStoreEntry) error) error { + if storageURI.Scheme != "local" { + return path.ErrBadPattern + } + root := path.Join(storageURI.Host, storageURI.Path) + if err := VerifyAbsPath(root, l.path, l.allowedPrefixes); err != nil { + return err + } + + var entries []*block.ObjectStoreEntry + // verify and use cache - location is stored in continuation token + if options.ContinuationToken != "" && strings.HasPrefix(options.ContinuationToken, l.cacheLocation) { + cacheData, err := os.ReadFile(options.ContinuationToken) + if err == nil { + err = json.Unmarshal(cacheData, &entries) + if err != nil { + entries = nil + } else { + l.mark.ContinuationToken = options.ContinuationToken + } + } + } + + // if needed scan all entries to import and calc etag + if entries == nil { + var err error + entries, err = l.scanEntries(root, options) + if err != nil { + return err + } + + // store entries to cache file + if l.cacheLocation != "" { + jsonData, err := json.Marshal(entries) + if err != nil { + return err + } + const dirPerm = 0o755 + _ = os.MkdirAll(l.cacheLocation, dirPerm) + cacheName := filepath.Join(l.cacheLocation, gonanoid.Must()+"-import.json") + const cachePerm = 0o644 + if err := os.WriteFile(cacheName, jsonData, cachePerm); err != nil { + _ = os.Remove(cacheName) + return err + } + l.mark.ContinuationToken = cacheName + } + } + + // search start position base on Last key + startIndex := sort.Search(len(entries), func(i int) bool { + return entries[i].FullKey > options.After + }) + for i := startIndex; i < len(entries); i++ { + ent := *entries[i] + etag, err := calcFileETag(ent) + if err != nil { + return err + } + + ent.ETag = etag + l.mark.LastKey = ent.FullKey + if err := walkFn(ent); err != nil { + return err + } + } + // delete cache in case we completed the iteration + if l.mark.ContinuationToken != "" { + if err := os.Remove(l.mark.ContinuationToken); err != nil { + return err + } + } + l.mark = block.Mark{} + return nil +} + +func (l *Walker) scanEntries(root string, options block.WalkOptions) ([]*block.ObjectStoreEntry, error) { + var entries []*block.ObjectStoreEntry + if err := filepath.Walk(root, func(p string, info fs.FileInfo, err error) error { + if err != nil { + return err + } + + // skip hidden files and directories + if !l.importHidden && strings.HasPrefix(info.Name(), ".") { + if info.IsDir() { + return fs.SkipDir + } + return nil + } + + key := filepath.ToSlash(p) + if key < options.After { + return nil + } + if !info.Mode().IsRegular() { + return nil + } + + addr := "local://" + key + relativePath, err := filepath.Rel(root, p) + if err != nil { + return err + } + // etag is calculated during iteration + ent := &block.ObjectStoreEntry{ + FullKey: key, + RelativeKey: filepath.ToSlash(relativePath), + Address: addr, + Mtime: info.ModTime(), + Size: info.Size(), + } + entries = append(entries, ent) + return nil + }); err != nil { + return nil, err + } + sort.Slice(entries, func(i, j int) bool { + return entries[i].FullKey < entries[j].FullKey + }) + return entries, nil +} + +func calcFileETag(ent block.ObjectStoreEntry) (string, error) { + f, err := os.Open(ent.FullKey) + if err != nil { + return "", err + } + defer func() { _ = f.Close() }() + hash := md5.New() //nolint:gosec + _, err = io.Copy(hash, f) + if err != nil { + return "", err + } + etag := hex.EncodeToString(hash.Sum(nil)) + return etag, nil +} + +func (l *Walker) Marker() block.Mark { + return l.mark +} + +func (l *Walker) GetSkippedEntries() []block.ObjectStoreEntry { + return nil +} diff --git a/block/mem/adapter.go b/block/mem/adapter.go new file mode 100644 index 00000000..240c2b48 --- /dev/null +++ b/block/mem/adapter.go @@ -0,0 +1,355 @@ +package mem + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/hex" + "fmt" + "io" + "net/http" + "net/url" + "sort" + "strings" + "sync" + "time" + + "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/block" +) + +var ( + ErrNoDataForKey = fmt.Errorf("no data for key: %w", block.ErrDataNotFound) + ErrMultiPartNotFound = fmt.Errorf("multipart ID not found") + ErrNoPropertiesForKey = fmt.Errorf("no properties for key") +) + +type mpu struct { + id string + parts map[int][]byte +} + +func newMPU() *mpu { + uid := uuid.New() + uploadID := hex.EncodeToString(uid[:]) + return &mpu{ + id: uploadID, + parts: make(map[int][]byte), + } +} + +func (m *mpu) get() []byte { + buf := bytes.NewBuffer(nil) + keys := make([]int, len(m.parts)) + sort.Slice(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + for _, part := range keys { + buf.Write(m.parts[part]) + } + return buf.Bytes() +} + +type Adapter struct { + data map[string][]byte + mpu map[string]*mpu + properties map[string]block.Properties + mutex *sync.RWMutex +} + +func New(_ context.Context, opts ...func(a *Adapter)) *Adapter { + a := &Adapter{ + data: make(map[string][]byte), + mpu: make(map[string]*mpu), + properties: make(map[string]block.Properties), + mutex: &sync.RWMutex{}, + } + for _, opt := range opts { + opt(a) + } + return a +} + +func getKey(obj block.ObjectPointer) string { + // TODO (niro): Fix mem storage path resolution + if obj.IdentifierType == block.IdentifierTypeFull { + return obj.Identifier + } + return fmt.Sprintf("%s:%s", obj.StorageNamespace, obj.Identifier) +} + +func (a *Adapter) Put(_ context.Context, obj block.ObjectPointer, _ int64, reader io.Reader, opts block.PutOpts) error { + if err := verifyObjectPointer(obj); err != nil { + return err + } + a.mutex.Lock() + defer a.mutex.Unlock() + data, err := io.ReadAll(reader) + if err != nil { + return err + } + key := getKey(obj) + a.data[key] = data + a.properties[key] = block.Properties(opts) + return nil +} + +func (a *Adapter) Get(_ context.Context, obj block.ObjectPointer, _ int64) (io.ReadCloser, error) { + if err := verifyObjectPointer(obj); err != nil { + return nil, err + } + a.mutex.RLock() + defer a.mutex.RUnlock() + key := getKey(obj) + data, ok := a.data[key] + if !ok { + return nil, ErrNoDataForKey + } + return io.NopCloser(bytes.NewReader(data)), nil +} + +func verifyObjectPointer(obj block.ObjectPointer) error { + const prefix = "mem://" + if obj.StorageNamespace == "" { + if !strings.HasPrefix(obj.Identifier, prefix) { + return fmt.Errorf("mem block adapter: %w identifier: %s", block.ErrInvalidAddress, obj.Identifier) + } + } else if !strings.HasPrefix(obj.StorageNamespace, prefix) { + return fmt.Errorf("mem block adapter: %w storage namespace: %s", block.ErrInvalidAddress, obj.StorageNamespace) + } + return nil +} + +func (a *Adapter) GetWalker(_ *url.URL) (block.Walker, error) { + return nil, fmt.Errorf("mem block adapter: %w", block.ErrOperationNotSupported) +} + +func (a *Adapter) GetPreSignedURL(_ context.Context, obj block.ObjectPointer, _ block.PreSignMode) (string, time.Time, error) { + if err := verifyObjectPointer(obj); err != nil { + return "", time.Time{}, err + } + return "", time.Time{}, fmt.Errorf("mem block adapter: %w", block.ErrOperationNotSupported) +} + +func (a *Adapter) Exists(_ context.Context, obj block.ObjectPointer) (bool, error) { + if err := verifyObjectPointer(obj); err != nil { + return false, err + } + a.mutex.RLock() + defer a.mutex.RUnlock() + _, ok := a.data[getKey(obj)] + return ok, nil +} + +func (a *Adapter) GetRange(_ context.Context, obj block.ObjectPointer, startPosition int64, endPosition int64) (io.ReadCloser, error) { + if err := verifyObjectPointer(obj); err != nil { + return nil, err + } + a.mutex.RLock() + defer a.mutex.RUnlock() + data, ok := a.data[getKey(obj)] + if !ok { + return nil, ErrNoDataForKey + } + return io.NopCloser(io.NewSectionReader(bytes.NewReader(data), startPosition, endPosition-startPosition+1)), nil +} + +func (a *Adapter) GetProperties(_ context.Context, obj block.ObjectPointer) (block.Properties, error) { + if err := verifyObjectPointer(obj); err != nil { + return block.Properties{}, err + } + a.mutex.RLock() + defer a.mutex.RUnlock() + props, ok := a.properties[getKey(obj)] + if !ok { + return block.Properties{}, ErrNoPropertiesForKey + } + return props, nil +} + +func (a *Adapter) Remove(_ context.Context, obj block.ObjectPointer) error { + if err := verifyObjectPointer(obj); err != nil { + return err + } + a.mutex.Lock() + defer a.mutex.Unlock() + delete(a.data, getKey(obj)) + return nil +} + +func (a *Adapter) Copy(_ context.Context, sourceObj, destinationObj block.ObjectPointer) error { + if err := verifyObjectPointer(sourceObj); err != nil { + return err + } + if err := verifyObjectPointer(destinationObj); err != nil { + return err + } + a.mutex.Lock() + defer a.mutex.Unlock() + destinationKey := getKey(destinationObj) + sourceKey := getKey(sourceObj) + a.data[destinationKey] = a.data[sourceKey] + a.properties[destinationKey] = a.properties[sourceKey] + return nil +} + +func (a *Adapter) UploadCopyPart(ctx context.Context, sourceObj, _ block.ObjectPointer, uploadID string, partNumber int) (*block.UploadPartResponse, error) { + if err := verifyObjectPointer(sourceObj); err != nil { + return nil, err + } + a.mutex.Lock() + defer a.mutex.Unlock() + mpu, ok := a.mpu[uploadID] + if !ok { + return nil, ErrMultiPartNotFound + } + entry, err := a.Get(ctx, sourceObj, 0) + if err != nil { + return nil, err + } + data, err := io.ReadAll(entry) + if err != nil { + return nil, err + } + h := sha256.New() + _, err = h.Write(data) + if err != nil { + return nil, err + } + code := h.Sum(nil) + mpu.parts[partNumber] = data + etag := fmt.Sprintf("%x", code) + return &block.UploadPartResponse{ + ETag: etag, + }, nil +} + +func (a *Adapter) UploadCopyPartRange(_ context.Context, sourceObj, _ block.ObjectPointer, uploadID string, partNumber int, startPosition, endPosition int64) (*block.UploadPartResponse, error) { + if err := verifyObjectPointer(sourceObj); err != nil { + return nil, err + } + a.mutex.Lock() + defer a.mutex.Unlock() + mpu, ok := a.mpu[uploadID] + if !ok { + return nil, ErrMultiPartNotFound + } + data, ok := a.data[getKey(sourceObj)] + if !ok { + return nil, ErrNoDataForKey + } + reader := io.NewSectionReader(bytes.NewReader(data), startPosition, endPosition-startPosition+1) + data, err := io.ReadAll(reader) + if err != nil { + return nil, err + } + h := sha256.New() + _, err = h.Write(data) + if err != nil { + return nil, err + } + code := h.Sum(nil) + mpu.parts[partNumber] = data + etag := fmt.Sprintf("%x", code) + return &block.UploadPartResponse{ + ETag: etag, + }, nil +} + +func (a *Adapter) CreateMultiPartUpload(_ context.Context, obj block.ObjectPointer, _ *http.Request, _ block.CreateMultiPartUploadOpts) (*block.CreateMultiPartUploadResponse, error) { + if err := verifyObjectPointer(obj); err != nil { + return nil, err + } + a.mutex.Lock() + defer a.mutex.Unlock() + mpu := newMPU() + a.mpu[mpu.id] = mpu + return &block.CreateMultiPartUploadResponse{ + UploadID: mpu.id, + }, nil +} + +func (a *Adapter) UploadPart(_ context.Context, obj block.ObjectPointer, _ int64, reader io.Reader, uploadID string, partNumber int) (*block.UploadPartResponse, error) { + if err := verifyObjectPointer(obj); err != nil { + return nil, err + } + a.mutex.Lock() + defer a.mutex.Unlock() + mpu, ok := a.mpu[uploadID] + if !ok { + return nil, ErrMultiPartNotFound + } + data, err := io.ReadAll(reader) + if err != nil { + return nil, err + } + h := sha256.New() + _, err = h.Write(data) + if err != nil { + return nil, err + } + code := h.Sum(nil) + mpu.parts[partNumber] = data + etag := fmt.Sprintf("%x", code) + return &block.UploadPartResponse{ + ETag: etag, + }, nil +} + +func (a *Adapter) AbortMultiPartUpload(_ context.Context, obj block.ObjectPointer, uploadID string) error { + if err := verifyObjectPointer(obj); err != nil { + return err + } + a.mutex.Lock() + defer a.mutex.Unlock() + _, ok := a.mpu[uploadID] + if !ok { + return ErrMultiPartNotFound + } + delete(a.mpu, uploadID) + return nil +} + +func (a *Adapter) CompleteMultiPartUpload(_ context.Context, obj block.ObjectPointer, uploadID string, _ *block.MultipartUploadCompletion) (*block.CompleteMultiPartUploadResponse, error) { + if err := verifyObjectPointer(obj); err != nil { + return nil, err + } + a.mutex.Lock() + defer a.mutex.Unlock() + mpu, ok := a.mpu[uploadID] + if !ok { + return nil, ErrMultiPartNotFound + } + data := mpu.get() + h := sha256.New() + _, err := h.Write(data) + if err != nil { + return nil, err + } + code := h.Sum(nil) + hexCode := fmt.Sprintf("%x", code) + a.data[getKey(obj)] = data + return &block.CompleteMultiPartUploadResponse{ + ETag: hexCode, + ContentLength: int64(len(data)), + }, nil +} + +func (a *Adapter) BlockstoreType() string { + return block.BlockstoreTypeMem +} + +func (a *Adapter) GetStorageNamespaceInfo() block.StorageNamespaceInfo { + info := block.DefaultStorageNamespaceInfo(block.BlockstoreTypeMem) + info.PreSignSupport = false + info.ImportSupport = false + return info +} + +func (a *Adapter) ResolveNamespace(storageNamespace, key string, identifierType block.IdentifierType) (block.QualifiedKey, error) { + return block.DefaultResolveNamespace(storageNamespace, key, identifierType) +} + +func (a *Adapter) RuntimeStats() map[string]string { + return nil +} diff --git a/block/mem/adapter_test.go b/block/mem/adapter_test.go new file mode 100644 index 00000000..0e316216 --- /dev/null +++ b/block/mem/adapter_test.go @@ -0,0 +1,7 @@ +package mem_test + +// TODO (niro): Need to enable +//func TestMemAdapter(t *testing.T) { +// adapter := mem.New() +// blocktest.TestAdapter(t, adapter, "") +//} diff --git a/block/namespace.go b/block/namespace.go new file mode 100644 index 00000000..9b2ef40f --- /dev/null +++ b/block/namespace.go @@ -0,0 +1,197 @@ +package block + +import ( + "fmt" + "net/url" + "strings" +) + +type StorageType int + +const ( + StorageTypeMem = iota + StorageTypeLocal + StorageTypeS3 + StorageTypeGS + StorageTypeAzure +) + +func (s StorageType) BlockstoreType() string { + switch s { + case StorageTypeAzure: + return "azure" + default: + return s.Scheme() + } +} + +func (s StorageType) Scheme() string { + scheme := "" + switch s { + case StorageTypeMem: + scheme = "mem" + case StorageTypeLocal: + scheme = "local" + case StorageTypeGS: + scheme = "gs" + case StorageTypeS3: + scheme = "s3" + case StorageTypeAzure: + scheme = "https" + default: + panic("unknown storage type") + } + return scheme +} + +type StorageNamespaceInfo struct { + ValidityRegex string // regex pattern that could be used to validate the namespace + Example string // example of a valid namespace + DefaultNamespacePrefix string // when a repo is created from the UI, suggest a default storage namespace under this prefix + PreSignSupport bool + PreSignSupportUI bool + ImportSupport bool + ImportValidityRegex string +} + +type QualifiedKey interface { + Format() string + GetStorageType() StorageType + GetStorageNamespace() string + GetKey() string +} + +type CommonQualifiedKey struct { + StorageType StorageType + StorageNamespace string + Key string +} + +func (qk CommonQualifiedKey) Format() string { + return qk.StorageType.Scheme() + "://" + formatPathWithNamespace(qk.StorageNamespace, qk.Key) +} + +func (qk CommonQualifiedKey) GetStorageType() StorageType { + return qk.StorageType +} + +func (qk CommonQualifiedKey) GetKey() string { + return qk.Key +} + +func (qk CommonQualifiedKey) GetStorageNamespace() string { + return qk.StorageNamespace +} + +func GetStorageType(namespaceURL *url.URL) (StorageType, error) { + var st StorageType + switch namespaceURL.Scheme { + case "s3": + return StorageTypeS3, nil + case "mem", "memory": + return StorageTypeMem, nil + case "local": + return StorageTypeLocal, nil + case "gs": + return StorageTypeGS, nil + case "http", "https": + return StorageTypeAzure, nil + default: + return st, fmt.Errorf("invalid storage scheme %s: %w", namespaceURL.Scheme, ErrInvalidAddress) + } +} + +func ValidateStorageType(uri *url.URL, expectedStorage StorageType) error { + storage, err := GetStorageType(uri) + if err != nil { + return err + } + + if storage != expectedStorage { + return fmt.Errorf("expected storage type %s: %w", expectedStorage.Scheme(), ErrInvalidAddress) + } + return nil +} + +func formatPathWithNamespace(namespacePath, keyPath string) string { + namespacePath = strings.Trim(namespacePath, "/") + if len(namespacePath) == 0 { + return strings.TrimPrefix(keyPath, "/") + } + return namespacePath + "/" + keyPath +} + +func DefaultResolveNamespace(defaultNamespace, key string, identifierType IdentifierType) (CommonQualifiedKey, error) { + switch identifierType { + case IdentifierTypeRelative: + return resolveRelative(defaultNamespace, key) + case IdentifierTypeFull: + return resolveFull(key) + default: + panic(fmt.Sprintf("unknown identifier type: %d", identifierType)) + } +} + +func resolveFull(key string) (CommonQualifiedKey, error) { + parsedKey, err := url.ParseRequestURI(key) + if err != nil { + return CommonQualifiedKey{}, fmt.Errorf("could not parse URI: %w", err) + } + // extract its scheme + storageType, err := GetStorageType(parsedKey) + if err != nil { + return CommonQualifiedKey{}, err + } + return CommonQualifiedKey{ + StorageType: storageType, + StorageNamespace: parsedKey.Host, + Key: formatPathWithNamespace("", parsedKey.Path), + }, nil +} + +func resolveRelative(defaultNamespace, key string) (CommonQualifiedKey, error) { + // is not fully qualified, treat as key only + // if we don't have a trailing slash for the namespace, add it. + parsedNS, err := url.ParseRequestURI(defaultNamespace) + if err != nil { + return CommonQualifiedKey{}, fmt.Errorf("default namespace %s: %w", defaultNamespace, ErrInvalidAddress) + } + storageType, err := GetStorageType(parsedNS) + if err != nil { + return CommonQualifiedKey{}, fmt.Errorf("no storage type for %s: %w", parsedNS, err) + } + + return CommonQualifiedKey{ + StorageType: storageType, + StorageNamespace: strings.TrimSuffix(parsedNS.Host+parsedNS.Path, "/"), + Key: key, + }, nil +} + +func resolveNamespaceUnknown(defaultNamespace, key string) (CommonQualifiedKey, error) { //nolint + // first try to treat key as a full path + if qk, err := resolveFull(key); err == nil { + return qk, nil + } + + // else, treat it as a relative path + return resolveRelative(defaultNamespace, key) +} + +func DefaultExample(scheme string) string { + return scheme + "://example-bucket/" +} + +func DefaultValidationRegex(scheme string) string { + return fmt.Sprintf("^%s://", scheme) +} + +func DefaultStorageNamespaceInfo(scheme string) StorageNamespaceInfo { + return StorageNamespaceInfo{ + ValidityRegex: DefaultValidationRegex(scheme), + Example: DefaultExample(scheme), + PreSignSupport: true, + ImportSupport: true, + ImportValidityRegex: DefaultValidationRegex(scheme), + } +} diff --git a/block/namespace_test.go b/block/namespace_test.go new file mode 100644 index 00000000..48325440 --- /dev/null +++ b/block/namespace_test.go @@ -0,0 +1,234 @@ +package block_test + +import ( + "errors" + "fmt" + "reflect" + "testing" + + "github.com/jiaozifs/jiaozifs/block" +) + +func TestResolveNamespace(t *testing.T) { + cases := []struct { + Name string + DefaultNamespace string + Key string + Type block.IdentifierType + ExpectedErr error + Expected block.CommonQualifiedKey + }{ + { + Name: "valid_namespace_no_trailing_slash", + DefaultNamespace: "s3://foo", + Key: "bar/baz", + Type: block.IdentifierTypeRelative, + ExpectedErr: nil, + Expected: block.CommonQualifiedKey{ + StorageType: block.StorageTypeS3, + StorageNamespace: "foo", + Key: "bar/baz", + }, + }, + { + Name: "valid_namespace_with_trailing_slash", + DefaultNamespace: "s3://foo/", + Key: "bar/baz", + Type: block.IdentifierTypeRelative, + ExpectedErr: nil, + Expected: block.CommonQualifiedKey{ + StorageType: block.StorageTypeS3, + StorageNamespace: "foo", + Key: "bar/baz", + }, + }, + { + Name: "valid_namespace_mem_with_trailing_slash", + DefaultNamespace: "mem://foo/", + Key: "bar/baz", + Type: block.IdentifierTypeRelative, + ExpectedErr: nil, + Expected: block.CommonQualifiedKey{ + StorageType: block.StorageTypeMem, + StorageNamespace: "foo", + Key: "bar/baz", + }, + }, + { + Name: "valid_namespace_with_prefix_and_trailing_slash", + DefaultNamespace: "gs://foo/bla/", + Key: "bar/baz", + Type: block.IdentifierTypeRelative, + ExpectedErr: nil, + Expected: block.CommonQualifiedKey{ + StorageType: block.StorageTypeGS, + StorageNamespace: "foo/bla", + Key: "bar/baz", + }, + }, + { + Name: "valid_namespace_with_prefix_and_no_trailing_slash", + DefaultNamespace: "gs://foo/bla", + Key: "bar/baz", + Type: block.IdentifierTypeRelative, + ExpectedErr: nil, + Expected: block.CommonQualifiedKey{ + StorageType: block.StorageTypeGS, + StorageNamespace: "foo/bla", + Key: "bar/baz", + }, + }, + { + Name: "valid_namespace_with_prefix_and_leading_key_slash", + DefaultNamespace: "gs://foo/bla", + Key: "/bar/baz", + Type: block.IdentifierTypeRelative, + ExpectedErr: nil, + Expected: block.CommonQualifiedKey{ + StorageType: block.StorageTypeGS, + StorageNamespace: "foo/bla", + Key: "/bar/baz", + }, + }, + { + Name: "valid_fq_key", + DefaultNamespace: "mem://foo/", + Key: "s3://example/bar/baz", + Type: block.IdentifierTypeFull, + ExpectedErr: nil, + Expected: block.CommonQualifiedKey{ + StorageType: block.StorageTypeS3, + StorageNamespace: "example", + Key: "bar/baz", + }, + }, + { + Name: "invalid_namespace_wrong_scheme", + DefaultNamespace: "memzzzz://foo/", + Key: "bar/baz", + Type: block.IdentifierTypeRelative, + ExpectedErr: block.ErrInvalidAddress, + Expected: block.CommonQualifiedKey{}, + }, + { + Name: "invalid_namespace_invalid_uri", + DefaultNamespace: "foo", + Key: "bar/baz", + Type: block.IdentifierTypeRelative, + ExpectedErr: block.ErrInvalidAddress, + Expected: block.CommonQualifiedKey{}, + }, + { + Name: "invalid_key_wrong_scheme", + DefaultNamespace: "s3://foo/", + Key: "s4://bar/baz", + Type: block.IdentifierTypeFull, + ExpectedErr: block.ErrInvalidAddress, + Expected: block.CommonQualifiedKey{}, + }, + { + Name: "key_weird_format", + DefaultNamespace: "s3://foo/", + Key: "://invalid/baz", + Type: block.IdentifierTypeRelative, + Expected: block.CommonQualifiedKey{ + StorageType: block.StorageTypeS3, + StorageNamespace: "foo", + Key: "://invalid/baz", + }, + }, + } + + for _, cas := range cases { + for _, r := range []block.IdentifierType{cas.Type} { + relativeName := "" + switch r { + case block.IdentifierTypeRelative: + relativeName = "relative" + case block.IdentifierTypeFull: + relativeName = "full" + } + t.Run(fmt.Sprintf("%s/%s", cas.Name, relativeName), func(t *testing.T) { + resolved, err := block.DefaultResolveNamespace(cas.DefaultNamespace, cas.Key, r) + if err != nil && !errors.Is(err, cas.ExpectedErr) { + t.Fatalf("got unexpected error :%v - expected %v", err, cas.ExpectedErr) + } + if cas.ExpectedErr == nil && !reflect.DeepEqual(resolved, cas.Expected) { + t.Fatalf("expected %v got %v", cas.Expected, resolved) + } + }) + } + } +} + +func TestFormatQualifiedKey(t *testing.T) { + cases := []struct { + Name string + QualifiedKey block.CommonQualifiedKey + Expected string + }{ + { + Name: "simple_path", + QualifiedKey: block.CommonQualifiedKey{ + StorageType: block.StorageTypeGS, + StorageNamespace: "some-bucket", + Key: "path", + }, + Expected: "gs://some-bucket/path", + }, + { + Name: "path_with_prefix", + QualifiedKey: block.CommonQualifiedKey{ + StorageType: block.StorageTypeS3, + StorageNamespace: "some-bucket/", + Key: "path/to/file", + }, + Expected: "s3://some-bucket/path/to/file", + }, + { + Name: "bucket_with_prefix", + QualifiedKey: block.CommonQualifiedKey{ + StorageType: block.StorageTypeS3, + StorageNamespace: "some-bucket/prefix/", + Key: "path/to/file", + }, + Expected: "s3://some-bucket/prefix/path/to/file", + }, + { + Name: "path_with_prefix_leading_slash", + QualifiedKey: block.CommonQualifiedKey{ + StorageType: block.StorageTypeS3, + StorageNamespace: "some-bucket", + Key: "/path/to/file", + }, + Expected: "s3://some-bucket//path/to/file", + }, + { + Name: "bucket_with_prefix_leading_slash", + QualifiedKey: block.CommonQualifiedKey{ + StorageType: block.StorageTypeS3, + StorageNamespace: "some-bucket/prefix", + Key: "/path/to/file", + }, + Expected: "s3://some-bucket/prefix//path/to/file", + }, + { + Name: "dont_eliminate_dots", + QualifiedKey: block.CommonQualifiedKey{ + StorageType: block.StorageTypeS3, + StorageNamespace: "some-bucket/prefix/", + Key: "path/to/../file", + }, + Expected: "s3://some-bucket/prefix/path/to/../file", + }, + } + + for _, cas := range cases { + t.Run(cas.Name, func(t *testing.T) { + formatted := cas.QualifiedKey.Format() + if formatted != cas.Expected { + t.Fatalf("Format() got '%s', expected '%s'", formatted, cas.Expected) + } + }) + } +} diff --git a/block/params/block.go b/block/params/block.go new file mode 100644 index 00000000..7a84e5e6 --- /dev/null +++ b/block/params/block.go @@ -0,0 +1,81 @@ +package params + +import ( + "time" +) + +// AdapterConfig configures a block adapter. +type AdapterConfig interface { + BlockstoreType() string + BlockstoreLocalParams() (Local, error) + BlockstoreS3Params() (S3, error) + BlockstoreGSParams() (GS, error) + BlockstoreAzureParams() (Azure, error) +} + +type Mem struct{} + +type Local struct { + Path string + ImportEnabled bool + ImportHidden bool + AllowedExternalPrefixes []string +} + +// S3WebIdentity contains parameters for customizing S3 web identity. This +// is also used when configuring S3 with IRSA in EKS (Kubernetes). +type S3WebIdentity struct { + // SessionDuration is the duration WebIdentityRoleProvider will + // request for a token for its assumed role. It can be 1 hour or + // more, but its maximum is configurable on AWS. + SessionDuration time.Duration + + // SessionExpiryWindow is the time before credentials expiry that + // the WebIdentityRoleProvider may request a fresh token. + SessionExpiryWindow time.Duration +} + +type S3Credentials struct { + AccessKeyID string + SecretAccessKey string + SessionToken string +} + +type S3 struct { + Region string + Profile string + CredentialsFile string + Credentials S3Credentials + MaxRetries int + Endpoint string + ForcePathStyle bool + DiscoverBucketRegion bool + SkipVerifyCertificateTestOnly bool + ServerSideEncryption string + ServerSideEncryptionKmsKeyID string + PreSignedExpiry time.Duration + DisablePreSigned bool + DisablePreSignedUI bool + ClientLogRetries bool + ClientLogRequest bool + WebIdentity *S3WebIdentity +} + +type GS struct { + CredentialsFile string + CredentialsJSON string + PreSignedExpiry time.Duration + DisablePreSigned bool + DisablePreSignedUI bool +} + +type Azure struct { + StorageAccount string + StorageAccessKey string + TryTimeout time.Duration + PreSignedExpiry time.Duration + DisablePreSigned bool + DisablePreSignedUI bool + // TestEndpointURL - For testing purposes, provide a custom URL to override the default URL template + TestEndpointURL string +} diff --git a/block/path.go b/block/path.go new file mode 100644 index 00000000..4a790409 --- /dev/null +++ b/block/path.go @@ -0,0 +1,119 @@ +package block + +import ( + "fmt" + "strings" +) + +const ( + Separator = "/" + + EntryTypeTree = "tree" + EntryTypeObject = "object" +) + +type Path struct { + str string + entryType string +} + +var RootPath = NewPath("", EntryTypeTree) + +func NewPath(str, entryType string) *Path { + return &Path{str, entryType} +} + +func (p *Path) String() string { + if p == nil { + return "" + } + joined := JoinPathParts(p.Split()) + return strings.TrimPrefix(joined, Separator) +} + +func (p *Path) Equals(other *Path) bool { + if p == nil && other == nil { + return true + } + if other == nil { + return false + } + if p.entryType != other.entryType { + return false + } + mine := p.Split() + theirs := other.Split() + if len(mine) != len(theirs) { + return false + } + for i, part := range mine { + if !strings.EqualFold(part, theirs[i]) { + return false + } + } + return true +} + +func (p *Path) Split() []string { + // trim first / if it exists + parts := strings.Split(p.str, Separator) + if len(parts) >= 2 && len(parts[0]) == 0 { + parts = parts[1:] + } + suffixedParts := make([]string, len(parts)) + for i, part := range parts { + suffixedPart := part + if i < len(parts)-1 { + suffixedPart = fmt.Sprintf("%s%s", part, Separator) + } + suffixedParts[i] = suffixedPart + } + if len(suffixedParts) >= 2 && p.entryType == EntryTypeTree && len(suffixedParts[len(suffixedParts)-1]) == 0 { + // remove empty suffix for tree type + suffixedParts = suffixedParts[:len(suffixedParts)-1] + } + return suffixedParts +} + +func (p *Path) BaseName() string { + var baseName string + parts := p.Split() + if len(parts) > 0 { + if len(parts) > 1 && len(parts[len(parts)-1]) == 0 && p.entryType == EntryTypeTree { + baseName = parts[len(parts)-2] + } else { + baseName = parts[len(parts)-1] + } + } + return baseName +} + +func (p *Path) ParentPath() string { + if p.IsRoot() { + return "" + } + parts := p.Split() + if len(parts) <= 1 { + return "" + } + if len(parts[len(parts)-1]) == 0 && p.entryType == EntryTypeTree { + return JoinPathParts(parts[:len(parts)-2]) + } + return JoinPathParts(parts[:len(parts)-1]) +} + +func (p *Path) IsRoot() bool { + return p.Equals(RootPath) +} + +func JoinPathParts(parts []string) string { + var buf strings.Builder + for pos, part := range parts { + buf.WriteString(part) + if pos != len(parts)-1 && !strings.HasSuffix(part, Separator) { + // if it's not the last part, and there's no separator at the end, add it + buf.WriteString(Separator) + } + } + return buf.String() +} diff --git a/block/path_test.go b/block/path_test.go new file mode 100644 index 00000000..edcedb60 --- /dev/null +++ b/block/path_test.go @@ -0,0 +1,156 @@ +package block_test + +import ( + "strings" + "testing" + + "github.com/davecgh/go-spew/spew" + "github.com/jiaozifs/jiaozifs/block" +) + +func equalStrings(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i, v := range a { + if v != b[i] { + return false + } + } + return true +} + +func TestPath_SplitParts_Objects(t *testing.T) { + testData := []struct { + Path string + Parts []string + }{ + {"/foo/bar", []string{"foo/", "bar"}}, + {"foo/bar/", []string{"foo/", "bar/", ""}}, + {"/foo///bar", []string{"foo/", "/", "/", "bar"}}, + {"/foo///bar/", []string{"foo/", "/", "/", "bar/", ""}}, + {"/foo///bar////", []string{"foo/", "/", "/", "bar/", "/", "/", "/", ""}}, + {"////foo", []string{"/", "/", "/", "foo"}}, + {"//", []string{"/", ""}}, + {"/", []string{""}}, + {"", []string{""}}, + {"/hello/world/another/level", []string{"hello/", "world/", "another/", "level"}}, + {"/hello/world/another/level/", []string{"hello/", "world/", "another/", "level/", ""}}, + } + for i, test := range testData { + p := block.NewPath(test.Path, block.EntryTypeObject) + if !equalStrings(p.Split(), test.Parts) { + t.Fatalf("expected (%d): %s, got %s for path: %s", i, spew.Sdump(test.Parts), spew.Sdump(p.Split()), test.Path) + } + } +} + +func TestPath_SplitParts_Trees(t *testing.T) { + testData := []struct { + Path string + Parts []string + }{ + {"//", []string{"/"}}, + {"/", []string{""}}, + {"", []string{""}}, + {"/foo/bar", []string{"foo/", "bar"}}, + {"foo/bar/", []string{"foo/", "bar/"}}, + {"/hello/world/another/level", []string{"hello/", "world/", "another/", "level"}}, + {"/hello/world/another/level/", []string{"hello/", "world/", "another/", "level/"}}, + } + for i, test := range testData { + p := block.NewPath(test.Path, block.EntryTypeTree) + if !equalStrings(p.Split(), test.Parts) { + t.Fatalf("expected (%d): %s, got %s for path: %s", i, spew.Sdump(test.Parts), spew.Sdump(p.Split()), test.Path) + } + } +} + +func TestPath_String(t *testing.T) { + var nilPath *block.Path + testData := []struct { + Path *block.Path + String string + }{ + {block.NewPath("hello/world/another/level", block.EntryTypeObject), "hello/world/another/level"}, + {block.NewPath("/hello/world/another/level", block.EntryTypeObject), "hello/world/another/level"}, + {block.NewPath("/hello/world/another/level/", block.EntryTypeTree), "hello/world/another/level/"}, + {nilPath, ""}, + } + for i, test := range testData { + if !strings.EqualFold(test.Path.String(), test.String) { + t.Fatalf("expected (%d): \"%s\", got \"%s\" for path: \"%s\"", i, test.String, test.Path.String(), test.Path) + } + } +} + +func TestJoin(t *testing.T) { + testData := []struct { + parts []string + expected string + }{ + {[]string{"foo/bar", "baz"}, "foo/bar/baz"}, + {[]string{"foo/bar/", "baz"}, "foo/bar/baz"}, + {[]string{"foo/bar", "", "baz"}, "foo/bar//baz"}, + {[]string{"foo//bar", "baz"}, "foo//bar/baz"}, + {[]string{"foo/bar", ""}, "foo/bar/"}, + {[]string{"foo/bar/", ""}, "foo/bar/"}, + } + for i, test := range testData { + got := block.JoinPathParts(test.parts) + if !strings.EqualFold(got, test.expected) { + t.Fatalf("expected (%d): '%s', got '%s' for %v", i, test.expected, got, test.parts) + } + } +} + +func TestPath_BaseName(t *testing.T) { + testData := []struct { + Path string + BaseName string + EntryType string + }{ + {"/foo", "foo", block.EntryTypeObject}, + {"/foo/bar", "bar", block.EntryTypeObject}, + {"", "", block.EntryTypeTree}, + {"/", "", block.EntryTypeTree}, + {"foo/bar", "bar", block.EntryTypeObject}, + {"foo/bar/", "", block.EntryTypeObject}, + {"foo/bar", "bar", block.EntryTypeTree}, + {"foo/bar/", "bar/", block.EntryTypeTree}, + } + for _, test := range testData { + p := block.NewPath(test.Path, test.EntryType) + if p.BaseName() != test.BaseName { + t.Fatalf("expected BaseName to return %s, got %s for input: %s", test.BaseName, p.BaseName(), test.Path) + } + } +} + +func TestPath_ParentPath(t *testing.T) { + testData := []struct { + Path string + ParentPath string + EntryType string + }{ + {"/", "", block.EntryTypeTree}, + {"foo", "", block.EntryTypeObject}, + {"/foo", "", block.EntryTypeObject}, + {"foo/", "", block.EntryTypeTree}, + {"foo/", "foo/", block.EntryTypeObject}, + {"/foo/bar", "foo/", block.EntryTypeObject}, + {"foo/bar", "foo/", block.EntryTypeObject}, + {"/foo/bar/", "foo/", block.EntryTypeTree}, + {"foo/bar/", "foo/bar/", block.EntryTypeObject}, + {"/foo/bar/baz", "foo/bar/", block.EntryTypeObject}, + {"foo/bar/baz", "foo/bar/", block.EntryTypeObject}, + {"/foo/bar/baz/", "foo/bar/", block.EntryTypeTree}, + {"/foo/bar/baz", "foo/bar/", block.EntryTypeTree}, + } + for _, test := range testData { + p := block.NewPath(test.Path, test.EntryType) + if p.ParentPath() != test.ParentPath { + t.Fatalf("expected ParentPath to return %s, got %s for input: %s", test.ParentPath, p.ParentPath(), test.Path) + } + } +} diff --git a/block/s3/adapter.go b/block/s3/adapter.go new file mode 100644 index 00000000..9df24ded --- /dev/null +++ b/block/s3/adapter.go @@ -0,0 +1,814 @@ +package s3 + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "sync/atomic" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/credentials/stscreds" + "github.com/aws/aws-sdk-go-v2/feature/s3/manager" + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/aws/aws-sdk-go-v2/service/s3/types" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/jiaozifs/jiaozifs/block" + "github.com/jiaozifs/jiaozifs/block/params" +) + +var ( + ErrS3 = errors.New("s3 error") + ErrMissingETag = fmt.Errorf("%w: missing ETag", ErrS3) +) + +type Adapter struct { + clients *ClientCache + respServer atomic.Pointer[string] + ServerSideEncryption string + ServerSideEncryptionKmsKeyID string + preSignedExpiry time.Duration + sessionExpiryWindow time.Duration + disablePreSigned bool + disablePreSignedUI bool +} + +func WithDiscoverBucketRegion(b bool) func(a *Adapter) { + return func(a *Adapter) { + a.clients.DiscoverBucketRegion(b) + } +} + +func WithPreSignedExpiry(v time.Duration) func(a *Adapter) { + return func(a *Adapter) { + a.preSignedExpiry = v + } +} + +func WithDisablePreSigned(b bool) func(a *Adapter) { + return func(a *Adapter) { + if b { + a.disablePreSigned = true + } + } +} + +func WithDisablePreSignedUI(b bool) func(a *Adapter) { + return func(a *Adapter) { + if b { + a.disablePreSignedUI = true + } + } +} + +func WithServerSideEncryption(s string) func(a *Adapter) { + return func(a *Adapter) { + a.ServerSideEncryption = s + } +} + +func WithServerSideEncryptionKmsKeyID(s string) func(a *Adapter) { + return func(a *Adapter) { + a.ServerSideEncryptionKmsKeyID = s + } +} + +type AdapterOption func(a *Adapter) + +func NewAdapter(ctx context.Context, params params.S3, opts ...AdapterOption) (*Adapter, error) { + cfg, err := LoadConfig(ctx, params) + if err != nil { + return nil, err + } + var sessionExpiryWindow time.Duration + if params.WebIdentity != nil { + sessionExpiryWindow = params.WebIdentity.SessionExpiryWindow + } + a := &Adapter{ + clients: NewClientCache(cfg, params), + preSignedExpiry: block.DefaultPreSignExpiryDuration, + sessionExpiryWindow: sessionExpiryWindow, + } + for _, opt := range opts { + opt(a) + } + return a, nil +} + +func LoadConfig(ctx context.Context, params params.S3) (aws.Config, error) { + var opts []func(*config.LoadOptions) error + + //opts = append(opts, config.WithLogger(log.With("sdk", "aws"))) todo adopt logger + var logMode aws.ClientLogMode + if params.ClientLogRetries { + logMode |= aws.LogRetries + } + if params.ClientLogRequest { + logMode |= aws.LogRequest + } + if logMode != 0 { + opts = append(opts, config.WithClientLogMode(logMode)) + } + if params.Region != "" { + opts = append(opts, config.WithRegion(params.Region)) + } + if params.Profile != "" { + opts = append(opts, config.WithSharedConfigProfile(params.Profile)) + } + if params.CredentialsFile != "" { + opts = append(opts, config.WithSharedCredentialsFiles([]string{params.CredentialsFile})) + } + if params.Credentials.AccessKeyID != "" { + opts = append(opts, config.WithCredentialsProvider( + credentials.NewStaticCredentialsProvider( + params.Credentials.AccessKeyID, + params.Credentials.SecretAccessKey, + params.Credentials.SessionToken, + ), + )) + } + if params.MaxRetries > 0 { + opts = append(opts, config.WithRetryMaxAttempts(params.MaxRetries)) + } + if params.SkipVerifyCertificateTestOnly { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec + } + opts = append(opts, config.WithHTTPClient(&http.Client{Transport: tr})) + } + if params.WebIdentity != nil { + wi := *params.WebIdentity // Copy WebIdentity: it will be used asynchronously. + if wi.SessionDuration > 0 { + opts = append(opts, config.WithWebIdentityRoleCredentialOptions( + func(options *stscreds.WebIdentityRoleOptions) { + options.Duration = wi.SessionDuration + }), + ) + } + if wi.SessionExpiryWindow > 0 { + opts = append(opts, config.WithCredentialsCacheOptions( + func(options *aws.CredentialsCacheOptions) { + options.ExpiryWindow = wi.SessionExpiryWindow + }), + ) + } + } + return config.LoadDefaultConfig(ctx, opts...) +} + +func WithClientParams(params params.S3) func(options *s3.Options) { + return func(options *s3.Options) { + if params.Endpoint != "" { + options.BaseEndpoint = aws.String(params.Endpoint) + } + if params.ForcePathStyle { + options.UsePathStyle = true + } + } +} + +func (a *Adapter) Put(ctx context.Context, obj block.ObjectPointer, sizeBytes int64, reader io.Reader, opts block.PutOpts) error { + var err error + defer reportMetrics("Put", time.Now(), &sizeBytes, &err) + + // for unknown size, we assume we like to stream content, will use s3manager to perform the request. + // we assume the caller may not have 1:1 request to s3 put object in this case as it may perform multipart upload + if sizeBytes == -1 { + return a.managerUpload(ctx, obj, reader, opts) + } + + bucket, key, _, err := a.extractParamsFromObj(obj) + if err != nil { + return err + } + + putObject := s3.PutObjectInput{ + Bucket: aws.String(bucket), + Key: aws.String(key), + Body: reader, + ContentLength: aws.Int64(sizeBytes), + } + if sizeBytes == 0 { + putObject.Body = http.NoBody + } + if opts.StorageClass != nil { + putObject.StorageClass = types.StorageClass(*opts.StorageClass) + } + if a.ServerSideEncryption != "" { + putObject.ServerSideEncryption = types.ServerSideEncryption(a.ServerSideEncryption) + } + if a.ServerSideEncryptionKmsKeyID != "" { + putObject.SSEKMSKeyId = aws.String(a.ServerSideEncryptionKmsKeyID) + } + + client := a.clients.Get(ctx, bucket) + resp, err := client.PutObject(ctx, &putObject, + retryMaxAttemptsByReader(reader), + s3.WithAPIOptions(v4.SwapComputePayloadSHA256ForUnsignedPayloadMiddleware), + a.registerCaptureServerMiddleware(), + ) + if err != nil { + return err + } + etag := aws.ToString(resp.ETag) + if etag == "" { + return ErrMissingETag + } + return nil +} + +// retryMaxAttemptsByReader return s3 options function +// setup RetryMaxAttempts - if the reader is not seekable, we can't retry the request +func retryMaxAttemptsByReader(reader io.Reader) func(*s3.Options) { + return func(o *s3.Options) { + if _, ok := reader.(io.Seeker); !ok { + o.RetryMaxAttempts = 1 + } + } +} + +// captureServerDeserializeMiddleware extracts the server name from the response and sets it on the block adapter +func (a *Adapter) captureServerDeserializeMiddleware(ctx context.Context, input middleware.DeserializeInput, handler middleware.DeserializeHandler) (middleware.DeserializeOutput, middleware.Metadata, error) { + output, m, err := handler.HandleDeserialize(ctx, input) + if err == nil { + if rawResponse, ok := output.RawResponse.(*smithyhttp.Response); ok { + s := rawResponse.Header.Get("Server") + if s != "" { + a.respServer.Store(&s) + } + } + } + return output, m, err +} + +func (a *Adapter) UploadPart(ctx context.Context, obj block.ObjectPointer, sizeBytes int64, reader io.Reader, uploadID string, partNumber int) (*block.UploadPartResponse, error) { + var err error + defer reportMetrics("UploadPart", time.Now(), &sizeBytes, &err) + bucket, key, _, err := a.extractParamsFromObj(obj) + if err != nil { + return nil, err + } + + uploadPartInput := &s3.UploadPartInput{ + Bucket: aws.String(bucket), + Key: aws.String(key), + PartNumber: aws.Int32(int32(partNumber)), + UploadId: aws.String(uploadID), + Body: reader, + ContentLength: aws.Int64(sizeBytes), + } + if a.ServerSideEncryption != "" { + uploadPartInput.SSECustomerAlgorithm = &a.ServerSideEncryption + } + if a.ServerSideEncryptionKmsKeyID != "" { + uploadPartInput.SSECustomerKey = &a.ServerSideEncryptionKmsKeyID + } + + client := a.clients.Get(ctx, bucket) + resp, err := client.UploadPart(ctx, uploadPartInput, + retryMaxAttemptsByReader(reader), + s3.WithAPIOptions(v4.SwapComputePayloadSHA256ForUnsignedPayloadMiddleware), + a.registerCaptureServerMiddleware(), + ) + if err != nil { + return nil, err + } + etag := aws.ToString(resp.ETag) + if etag == "" { + return nil, ErrMissingETag + } + return &block.UploadPartResponse{ + ETag: strings.Trim(etag, `"`), + ServerSideHeader: extractSSHeaderUploadPart(resp), + }, nil +} + +func isErrNotFound(err error) bool { + var ( + errNoSuchKey *types.NoSuchKey + errNotFound *types.NotFound + ) + return errors.As(err, &errNoSuchKey) || errors.As(err, &errNotFound) +} + +func (a *Adapter) Get(ctx context.Context, obj block.ObjectPointer, _ int64) (io.ReadCloser, error) { + var err error + var sizeBytes int64 + defer reportMetrics("Get", time.Now(), &sizeBytes, &err) + log := log.With("operation", "GetObject") + bucket, key, qualifiedKey, err := a.extractParamsFromObj(obj) + if err != nil { + return nil, err + } + + getObjectInput := s3.GetObjectInput{ + Bucket: aws.String(bucket), + Key: aws.String(key), + } + client := a.clients.Get(ctx, bucket) + objectOutput, err := client.GetObject(ctx, &getObjectInput) + if isErrNotFound(err) { + return nil, block.ErrDataNotFound + } + if err != nil { + log.Errorf("failed to get S3 object bucket %s key %s %v", qualifiedKey.GetStorageNamespace(), qualifiedKey.GetKey(), err) + return nil, err + } + sizeBytes = *objectOutput.ContentLength + return objectOutput.Body, nil +} + +func (a *Adapter) GetWalker(uri *url.URL) (block.Walker, error) { + if err := block.ValidateStorageType(uri, block.StorageTypeS3); err != nil { + return nil, err + } + return NewS3Walker(a.clients.GetDefault()), nil +} + +type CaptureExpiresPresigner struct { + Presigner s3.HTTPPresignerV4 + CredentialsCanExpire bool + CredentialsExpireAt time.Time +} + +func (c *CaptureExpiresPresigner) PresignHTTP(ctx context.Context, credentials aws.Credentials, r *http.Request, payloadHash string, service string, region string, signingTime time.Time, optFns ...func(*v4.SignerOptions)) (url string, signedHeader http.Header, err error) { + // capture credentials expiry + c.CredentialsCanExpire = credentials.CanExpire + c.CredentialsExpireAt = credentials.Expires + return c.Presigner.PresignHTTP(ctx, credentials, r, payloadHash, service, region, signingTime, optFns...) +} + +func (a *Adapter) GetPreSignedURL(ctx context.Context, obj block.ObjectPointer, mode block.PreSignMode) (string, time.Time, error) { + if a.disablePreSigned { + return "", time.Time{}, block.ErrOperationNotSupported + } + + expiry := time.Now().Add(a.preSignedExpiry) + + log := log.With( + "operation", "GetPreSignedURL", + "namespace", obj.StorageNamespace, + "identifier", obj.Identifier, + "ttl", time.Until(expiry), + ) + bucket, key, _, err := a.extractParamsFromObj(obj) + if err != nil { + log.Errorf("could not resolve namespace %v", err) + return "", time.Time{}, err + } + + client := a.clients.Get(ctx, bucket) + presigner := s3.NewPresignClient(client, + func(options *s3.PresignOptions) { + options.Expires = a.preSignedExpiry + }) + + captureExpiresPresigner := &CaptureExpiresPresigner{} + var req *v4.PresignedHTTPRequest + if mode == block.PreSignModeWrite { + putObjectInput := &s3.PutObjectInput{ + Bucket: aws.String(bucket), + Key: aws.String(key), + } + req, err = presigner.PresignPutObject(ctx, putObjectInput, func(o *s3.PresignOptions) { + captureExpiresPresigner.Presigner = o.Presigner + o.Presigner = captureExpiresPresigner + }) + } else { + getObjectInput := &s3.GetObjectInput{ + Bucket: aws.String(bucket), + Key: aws.String(key), + } + req, err = presigner.PresignGetObject(ctx, getObjectInput, func(o *s3.PresignOptions) { + captureExpiresPresigner.Presigner = o.Presigner + o.Presigner = captureExpiresPresigner + }) + } + if err != nil { + log.Errorf("could not pre-sign request %v", err) + return "", time.Time{}, err + } + + // In case the credentials can expire, we need to use the earliest expiry time + // we assume that session expiry window is used and adjust the expiry time accordingly. + // AWS Go SDK v2 stores the time to renew credentials in `CredentialsExpireAt`. This is + // a.sessionExpiryWindow before actual credentials expiry. + if captureExpiresPresigner.CredentialsCanExpire && captureExpiresPresigner.CredentialsExpireAt.Before(expiry) { + expiry = captureExpiresPresigner.CredentialsExpireAt.Add(a.sessionExpiryWindow) + } + return req.URL, expiry, nil +} + +func (a *Adapter) Exists(ctx context.Context, obj block.ObjectPointer) (bool, error) { + var err error + defer reportMetrics("Exists", time.Now(), nil, &err) + log := log.With("operation", "HeadObject") + bucket, key, _, err := a.extractParamsFromObj(obj) + if err != nil { + return false, err + } + + input := s3.HeadObjectInput{ + Bucket: aws.String(bucket), + Key: aws.String(key), + } + client := a.clients.Get(ctx, bucket) + _, err = client.HeadObject(ctx, &input) + if isErrNotFound(err) { + return false, nil + } + if err != nil { + log.Errorf("failed to stat S3 object %v", err) + return false, err + } + return true, nil +} + +func (a *Adapter) GetRange(ctx context.Context, obj block.ObjectPointer, startPosition int64, endPosition int64) (io.ReadCloser, error) { + var err error + var sizeBytes int64 + defer reportMetrics("GetRange", time.Now(), &sizeBytes, &err) + bucket, key, _, err := a.extractParamsFromObj(obj) + if err != nil { + return nil, err + } + log := log.With("operation", "GetObjectRange") + getObjectInput := s3.GetObjectInput{ + Bucket: aws.String(bucket), + Key: aws.String(key), + Range: aws.String(fmt.Sprintf("bytes=%d-%d", startPosition, endPosition)), + } + client := a.clients.Get(ctx, bucket) + objectOutput, err := client.GetObject(ctx, &getObjectInput) + if isErrNotFound(err) { + return nil, block.ErrDataNotFound + } + if err != nil { + log.With( + "start_position", startPosition, + "end_position", endPosition, + ).Errorf("failed to get S3 object range %v", err) + return nil, err + } + sizeBytes = *objectOutput.ContentLength + return objectOutput.Body, nil +} + +func (a *Adapter) GetProperties(ctx context.Context, obj block.ObjectPointer) (block.Properties, error) { + var err error + defer reportMetrics("GetProperties", time.Now(), nil, &err) + bucket, key, _, err := a.extractParamsFromObj(obj) + if err != nil { + return block.Properties{}, err + } + + headObjectParams := &s3.HeadObjectInput{ + Bucket: aws.String(bucket), + Key: aws.String(key), + } + client := a.clients.Get(ctx, bucket) + s3Props, err := client.HeadObject(ctx, headObjectParams) + if err != nil { + return block.Properties{}, err + } + return block.Properties{ + StorageClass: aws.String(string(s3Props.StorageClass)), + }, nil +} + +func (a *Adapter) Remove(ctx context.Context, obj block.ObjectPointer) error { + var err error + defer reportMetrics("Remove", time.Now(), nil, &err) + bucket, key, _, err := a.extractParamsFromObj(obj) + if err != nil { + return err + } + + deleteInput := &s3.DeleteObjectInput{ + Bucket: aws.String(bucket), + Key: aws.String(key), + } + client := a.clients.Get(ctx, bucket) + _, err = client.DeleteObject(ctx, deleteInput) + if err != nil { + log.Errorf("failed to delete S3 object %v", err) + return err + } + + headInput := &s3.HeadObjectInput{ + Bucket: aws.String(bucket), + Key: aws.String(key), + } + const maxWaitDur = 100 * time.Second + waiter := s3.NewObjectNotExistsWaiter(client) + return waiter.Wait(ctx, headInput, maxWaitDur) +} + +func (a *Adapter) copyPart(ctx context.Context, sourceObj, destinationObj block.ObjectPointer, uploadID string, partNumber int, byteRange *string) (*block.UploadPartResponse, error) { + srcKey, err := resolveNamespace(sourceObj) + if err != nil { + return nil, err + } + + bucket, key, _, err := a.extractParamsFromObj(destinationObj) + if err != nil { + return nil, err + } + + uploadPartCopyObject := s3.UploadPartCopyInput{ + Bucket: aws.String(bucket), + Key: aws.String(key), + PartNumber: aws.Int32(int32(partNumber)), + UploadId: aws.String(uploadID), + CopySource: aws.String(fmt.Sprintf("%s/%s", srcKey.GetStorageNamespace(), srcKey.GetKey())), + } + if byteRange != nil { + uploadPartCopyObject.CopySourceRange = byteRange + } + client := a.clients.Get(ctx, bucket) + resp, err := client.UploadPartCopy(ctx, &uploadPartCopyObject) + if err != nil { + return nil, err + } + if resp == nil || resp.CopyPartResult == nil || resp.CopyPartResult.ETag == nil { + return nil, ErrMissingETag + } + + etag := strings.Trim(*resp.CopyPartResult.ETag, `"`) + return &block.UploadPartResponse{ + ETag: etag, + ServerSideHeader: extractSSHeaderUploadPartCopy(resp), + }, nil +} + +func (a *Adapter) UploadCopyPart(ctx context.Context, sourceObj, destinationObj block.ObjectPointer, uploadID string, partNumber int) (*block.UploadPartResponse, error) { + var err error + defer reportMetrics("UploadCopyPart", time.Now(), nil, &err) + return a.copyPart(ctx, sourceObj, destinationObj, uploadID, partNumber, nil) +} + +func (a *Adapter) UploadCopyPartRange(ctx context.Context, sourceObj, destinationObj block.ObjectPointer, uploadID string, partNumber int, startPosition, endPosition int64) (*block.UploadPartResponse, error) { + var err error + defer reportMetrics("UploadCopyPartRange", time.Now(), nil, &err) + return a.copyPart(ctx, + sourceObj, destinationObj, uploadID, partNumber, + aws.String(fmt.Sprintf("bytes=%d-%d", startPosition, endPosition))) +} + +func (a *Adapter) Copy(ctx context.Context, sourceObj, destinationObj block.ObjectPointer) error { + var err error + defer reportMetrics("Copy", time.Now(), nil, &err) + qualifiedSourceKey, err := resolveNamespace(sourceObj) + if err != nil { + return err + } + + destBucket, destKey, _, err := a.extractParamsFromObj(destinationObj) + if err != nil { + return err + } + + copyObjectInput := &s3.CopyObjectInput{ + Bucket: aws.String(destBucket), + Key: aws.String(destKey), + CopySource: aws.String(qualifiedSourceKey.GetStorageNamespace() + "/" + qualifiedSourceKey.GetKey()), + } + if a.ServerSideEncryption != "" { + copyObjectInput.ServerSideEncryption = types.ServerSideEncryption(a.ServerSideEncryption) + } + if a.ServerSideEncryptionKmsKeyID != "" { + copyObjectInput.SSEKMSKeyId = aws.String(a.ServerSideEncryptionKmsKeyID) + } + _, err = a.clients.Get(ctx, destBucket).CopyObject(ctx, copyObjectInput) + if err != nil { + log.Errorf("failed to copy S3 object %v", err) + } + return err +} + +func (a *Adapter) CreateMultiPartUpload(ctx context.Context, obj block.ObjectPointer, _ *http.Request, opts block.CreateMultiPartUploadOpts) (*block.CreateMultiPartUploadResponse, error) { + var err error + defer reportMetrics("CreateMultiPartUpload", time.Now(), nil, &err) + bucket, key, qualifiedKey, err := a.extractParamsFromObj(obj) + if err != nil { + return nil, err + } + + input := &s3.CreateMultipartUploadInput{ + Bucket: aws.String(bucket), + Key: aws.String(key), + ContentType: aws.String(""), + } + if opts.StorageClass != nil { + input.StorageClass = types.StorageClass(*opts.StorageClass) + } + if a.ServerSideEncryption != "" { + input.ServerSideEncryption = types.ServerSideEncryption(a.ServerSideEncryption) + } + if a.ServerSideEncryptionKmsKeyID != "" { + input.SSEKMSKeyId = &a.ServerSideEncryptionKmsKeyID + } + client := a.clients.Get(ctx, bucket) + resp, err := client.CreateMultipartUpload(ctx, input) + if err != nil { + return nil, err + } + uploadID := aws.ToString(resp.UploadId) + log.With( + "upload_id", uploadID, + "qualified_ns", qualifiedKey.GetStorageNamespace(), + "qualified_key", qualifiedKey.GetKey(), + "key", obj.Identifier, + ).Debug("created multipart upload") + return &block.CreateMultiPartUploadResponse{ + UploadID: uploadID, + ServerSideHeader: extractSSHeaderCreateMultipartUpload(resp), + }, err +} + +func (a *Adapter) AbortMultiPartUpload(ctx context.Context, obj block.ObjectPointer, uploadID string) error { + var err error + defer reportMetrics("AbortMultiPartUpload", time.Now(), nil, &err) + bucket, key, qualifiedKey, err := a.extractParamsFromObj(obj) + if err != nil { + return err + } + input := &s3.AbortMultipartUploadInput{ + Bucket: aws.String(bucket), + Key: aws.String(key), + UploadId: aws.String(uploadID), + } + + client := a.clients.Get(ctx, bucket) + _, err = client.AbortMultipartUpload(ctx, input) + lg := log.With( + "upload_id", uploadID, + "qualified_ns", qualifiedKey.GetStorageNamespace(), + "qualified_key", qualifiedKey.GetKey(), + "key", obj.Identifier, + ) + if err != nil { + lg.Error("Failed to abort multipart upload") + return err + } + lg.Debug("aborted multipart upload") + return nil +} + +func convertFromBlockMultipartUploadCompletion(multipartList *block.MultipartUploadCompletion) *types.CompletedMultipartUpload { + parts := make([]types.CompletedPart, 0, len(multipartList.Part)) + for _, p := range multipartList.Part { + parts = append(parts, types.CompletedPart{ + ETag: aws.String(p.ETag), + PartNumber: aws.Int32(int32(p.PartNumber)), + }) + } + return &types.CompletedMultipartUpload{Parts: parts} +} + +func (a *Adapter) CompleteMultiPartUpload(ctx context.Context, obj block.ObjectPointer, uploadID string, multipartList *block.MultipartUploadCompletion) (*block.CompleteMultiPartUploadResponse, error) { + var err error + defer reportMetrics("CompleteMultiPartUpload", time.Now(), nil, &err) + bucket, key, qualifiedKey, err := a.extractParamsFromObj(obj) + if err != nil { + return nil, err + } + input := &s3.CompleteMultipartUploadInput{ + Bucket: aws.String(bucket), + Key: aws.String(key), + UploadId: aws.String(uploadID), + MultipartUpload: convertFromBlockMultipartUploadCompletion(multipartList), + } + lg := log.With( + "upload_id", uploadID, + "qualified_ns", qualifiedKey.GetStorageNamespace(), + "qualified_key", qualifiedKey.GetKey(), + "key", obj.Identifier, + ) + client := a.clients.Get(ctx, bucket) + resp, err := client.CompleteMultipartUpload(ctx, input) + if err != nil { + lg.Errorf("CompleteMultipartUpload failed %v", err) + return nil, err + } + lg.Debug("completed multipart upload") + headInput := &s3.HeadObjectInput{Bucket: &bucket, Key: &key} + headResp, err := client.HeadObject(ctx, headInput) + if err != nil { + return nil, err + } + + etag := strings.Trim(aws.ToString(resp.ETag), `"`) + return &block.CompleteMultiPartUploadResponse{ + ETag: etag, + ContentLength: *headResp.ContentLength, + ServerSideHeader: extractSSHeaderCompleteMultipartUpload(resp), + }, nil +} + +func (a *Adapter) BlockstoreType() string { + return block.BlockstoreTypeS3 +} + +func (a *Adapter) GetStorageNamespaceInfo() block.StorageNamespaceInfo { + info := block.DefaultStorageNamespaceInfo(block.BlockstoreTypeS3) + if a.disablePreSigned { + info.PreSignSupport = false + } + if !(a.disablePreSignedUI || a.disablePreSigned) { + info.PreSignSupportUI = true + } + return info +} + +func resolveNamespace(obj block.ObjectPointer) (block.CommonQualifiedKey, error) { + qualifiedKey, err := block.DefaultResolveNamespace(obj.StorageNamespace, obj.Identifier, obj.IdentifierType) + if err != nil { + return qualifiedKey, err + } + if qualifiedKey.GetStorageType() != block.StorageTypeS3 { + return qualifiedKey, fmt.Errorf("expected storage type s3: %w", block.ErrInvalidAddress) + } + return qualifiedKey, nil +} + +func (a *Adapter) ResolveNamespace(storageNamespace, key string, identifierType block.IdentifierType) (block.QualifiedKey, error) { + return block.DefaultResolveNamespace(storageNamespace, key, identifierType) +} + +func (a *Adapter) RuntimeStats() map[string]string { + respServer := aws.ToString(a.respServer.Load()) + if respServer == "" { + return nil + } + return map[string]string{ + "resp_server": respServer, + } +} + +func (a *Adapter) managerUpload(ctx context.Context, obj block.ObjectPointer, reader io.Reader, opts block.PutOpts) error { + bucket, key, _, err := a.extractParamsFromObj(obj) + if err != nil { + return err + } + + client := a.clients.Get(ctx, bucket) + uploader := manager.NewUploader(client) + input := &s3.PutObjectInput{ + Bucket: aws.String(bucket), + Key: aws.String(key), + Body: reader, + } + if opts.StorageClass != nil { + input.StorageClass = types.StorageClass(*opts.StorageClass) + } + if a.ServerSideEncryption != "" { + input.ServerSideEncryption = types.ServerSideEncryption(a.ServerSideEncryption) + } + if a.ServerSideEncryptionKmsKeyID != "" { + input.SSEKMSKeyId = aws.String(a.ServerSideEncryptionKmsKeyID) + } + + output, err := uploader.Upload(ctx, input) + if err != nil { + return err + } + if aws.ToString(output.ETag) == "" { + return ErrMissingETag + } + return nil +} + +func (a *Adapter) extractParamsFromObj(obj block.ObjectPointer) (string, string, block.QualifiedKey, error) { + qk, err := a.ResolveNamespace(obj.StorageNamespace, obj.Identifier, obj.IdentifierType) + if err != nil { + return "", "", nil, err + } + bucket, key := ExtractParamsFromQK(qk) + return bucket, key, qk, nil +} + +func (a *Adapter) registerCaptureServerMiddleware() func(*s3.Options) { + fn := middleware.DeserializeMiddlewareFunc("ResponseServerValue", a.captureServerDeserializeMiddleware) + return s3.WithAPIOptions(func(stack *middleware.Stack) error { + return stack.Deserialize.Add(fn, middleware.After) + }) +} + +func ExtractParamsFromQK(qk block.QualifiedKey) (string, string) { + bucket, prefix, _ := strings.Cut(qk.GetStorageNamespace(), "/") + key := qk.GetKey() + if len(prefix) > 0 { // Avoid situations where prefix is empty or "/" + key = prefix + "/" + key + } + return bucket, key +} diff --git a/block/s3/adapter_test.go b/block/s3/adapter_test.go new file mode 100644 index 00000000..6a368dae --- /dev/null +++ b/block/s3/adapter_test.go @@ -0,0 +1,86 @@ +package s3_test + +import ( + "context" + "net/url" + "regexp" + "testing" + + "github.com/jiaozifs/jiaozifs/block/blocktest" + "github.com/jiaozifs/jiaozifs/block/params" + "github.com/jiaozifs/jiaozifs/block/s3" + "github.com/stretchr/testify/require" +) + +func getS3BlockAdapter(t *testing.T) *s3.Adapter { + s3params := params.S3{ + Region: "us-east-1", + Endpoint: blockURL, + ForcePathStyle: true, + DiscoverBucketRegion: false, + Credentials: params.S3Credentials{ + AccessKeyID: minioTestAccessKeyID, + SecretAccessKey: minioTestSecretAccessKey, + }, + } + adapter, err := s3.NewAdapter(context.Background(), s3params) + if err != nil { + t.Fatal("cannot create s3 adapter: ", err) + } + return adapter +} + +func TestS3Adapter(t *testing.T) { + basePath, err := url.JoinPath("s3://", bucketName) + require.NoError(t, err) + localPath, err := url.JoinPath(basePath, "lakefs") + require.NoError(t, err) + externalPath, err := url.JoinPath(basePath, "external") + require.NoError(t, err) + + adapter := getS3BlockAdapter(t) + blocktest.AdapterTest(t, adapter, localPath, externalPath) +} + +func TestAdapterNamespace(t *testing.T) { + adapter := getS3BlockAdapter(t) + expr, err := regexp.Compile(adapter.GetStorageNamespaceInfo().ValidityRegex) + require.NoError(t, err) + + tests := []struct { + Name string + Namespace string + Success bool + }{ + { + Name: "valid_path", + Namespace: "s3://bucket/path/to/repo1", + Success: true, + }, + { + Name: "double_slash", + Namespace: "s3://bucket/path//to/repo1", + Success: true, + }, + { + Name: "invalid_schema", + Namespace: "s3:/test/adls/core/windows/net", + Success: false, + }, + { + Name: "invalid_path", + Namespace: "https://test/adls/core/windows/net", + Success: false, + }, + { + Name: "invalid_string", + Namespace: "this is a bad string", + Success: false, + }, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + require.Equal(t, tt.Success, expr.MatchString(tt.Namespace)) + }) + } +} diff --git a/block/s3/client_cache.go b/block/s3/client_cache.go new file mode 100644 index 00000000..d2ae08f6 --- /dev/null +++ b/block/s3/client_cache.go @@ -0,0 +1,147 @@ +package s3 + +import ( + "context" + "sync" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/feature/s3/manager" + "github.com/aws/aws-sdk-go-v2/service/s3" + logging "github.com/ipfs/go-log/v2" + "github.com/jiaozifs/jiaozifs/block/params" +) + +var log = logging.Logger("s3") + +type ( + clientFactory func(region string) *s3.Client + s3RegionGetter func(ctx context.Context, bucket string) (string, error) +) + +type ClientCache struct { + mu sync.Mutex + regionClient map[string]*s3.Client + bucketRegion map[string]string + awsConfig aws.Config + defaultClient *s3.Client + clientFactory clientFactory + s3RegionGetter s3RegionGetter +} + +func NewClientCache(awsConfig aws.Config, params params.S3) *ClientCache { + clientFactory := newClientFactory(awsConfig, WithClientParams(params)) + defaultClient := clientFactory(awsConfig.Region) + clientCache := &ClientCache{ + regionClient: make(map[string]*s3.Client), + bucketRegion: make(map[string]string), + awsConfig: awsConfig, + defaultClient: defaultClient, + clientFactory: clientFactory, + } + clientCache.DiscoverBucketRegion(true) + return clientCache +} + +// newClientFactory returns a function that creates a new S3 client with the given region. +// accepts aws configuration and list of s3 options functions to apply with the s3 client. +// the factory function is used to create a new client for a region when it is not cached. +func newClientFactory(awsConfig aws.Config, s3OptFns ...func(options *s3.Options)) clientFactory { + return func(region string) *s3.Client { + return s3.NewFromConfig(awsConfig, func(options *s3.Options) { + for _, opts := range s3OptFns { + opts(options) + } + options.Region = region + }) + } +} + +func (c *ClientCache) SetClientFactory(clientFactory clientFactory) { + c.clientFactory = clientFactory +} + +func (c *ClientCache) SetS3RegionGetter(s3RegionGetter s3RegionGetter) { + c.s3RegionGetter = s3RegionGetter +} + +func (c *ClientCache) DiscoverBucketRegion(b bool) { + if b { + c.s3RegionGetter = c.getBucketRegionFromAWS + } else { + c.s3RegionGetter = c.getBucketRegionDefault + } +} + +func (c *ClientCache) getBucketRegionFromAWS(ctx context.Context, bucket string) (string, error) { + return manager.GetBucketRegion(ctx, c.defaultClient, bucket) +} + +func (c *ClientCache) getBucketRegionDefault(_ context.Context, _ string) (string, error) { + return c.awsConfig.Region, nil +} + +func (c *ClientCache) Get(ctx context.Context, bucket string) *s3.Client { + client, region := c.cachedClientByBucket(bucket) + if client != nil { + return client + } + + // lookup region if needed + if region == "" { + region = c.refreshBucketRegion(ctx, bucket) + if client, ok := c.cachedClientByRegion(region); ok { + return client + } + } + + // create client and update cache + log.With("region", region).Debug("creating client for region") + client = c.clientFactory(region) + + // re-check if a client was created by another goroutine + // keep using the existing client and discard the new one + c.mu.Lock() + existingClient, existingFound := c.regionClient[region] + if existingFound { + client = existingClient + } else { + c.regionClient[region] = client + } + c.mu.Unlock() + return client +} + +func (c *ClientCache) cachedClientByBucket(bucket string) (*s3.Client, string) { + c.mu.Lock() + defer c.mu.Unlock() + if region, ok := c.bucketRegion[bucket]; ok { + return c.regionClient[region], region + } + return nil, "" +} + +func (c *ClientCache) cachedClientByRegion(region string) (*s3.Client, bool) { + c.mu.Lock() + defer c.mu.Unlock() + client, ok := c.regionClient[region] + return client, ok +} + +func (c *ClientCache) refreshBucketRegion(ctx context.Context, bucket string) string { + region, err := c.s3RegionGetter(ctx, bucket) + if err != nil { + // fallback to default region + region = c.awsConfig.Region + log.With("default_region", region). + Error("Failed to get region for bucket, falling back to default region") + } + // update bucket to region cache + c.mu.Lock() + c.bucketRegion[bucket] = region + c.mu.Unlock() + return region +} + +func (c *ClientCache) GetDefault() *s3.Client { + return c.defaultClient +} diff --git a/block/s3/client_cache_test.go b/block/s3/client_cache_test.go new file mode 100644 index 00000000..3c9255e2 --- /dev/null +++ b/block/s3/client_cache_test.go @@ -0,0 +1,114 @@ +package s3_test + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/aws/aws-sdk-go-v2/config" + awss3 "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/go-test/deep" + "github.com/jiaozifs/jiaozifs/block/params" + "github.com/jiaozifs/jiaozifs/block/s3" +) + +var errRegion = errors.New("failed to get region") + +func TestClientCache(t *testing.T) { + const defaultRegion = "us-west-2" + ctx := context.Background() + cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(defaultRegion)) + require.NoError(t, err) + + tests := []struct { + name string + bucketToRegion map[string]string + bucketCalls []string + regionErrorsIndexes map[int]bool + }{ + { + name: "two_buckets_two_regions", + bucketToRegion: map[string]string{"us-bucket": "us-east-1", "eu-bucket": "eu-west-1"}, + bucketCalls: []string{"us-bucket", "us-bucket", "us-bucket", "eu-bucket", "eu-bucket", "eu-bucket"}, + }, + { + name: "multiple_buckets_two_regions", + bucketToRegion: map[string]string{"us-bucket-1": "us-east-1", "us-bucket-2": "us-east-1", "us-bucket-3": "us-east-1", "eu-bucket-1": "eu-west-1", "eu-bucket-2": "eu-west-1"}, + bucketCalls: []string{"us-bucket-1", "us-bucket-2", "us-bucket-3", "eu-bucket-1", "eu-bucket-2"}, + }, + { + name: "error_on_get_region", + bucketToRegion: map[string]string{"us-bucket": "us-east-1", "eu-bucket": "eu-west-1"}, + bucketCalls: []string{"us-bucket", "us-bucket", "us-bucket", "eu-bucket", "eu-bucket", "eu-bucket"}, + regionErrorsIndexes: map[int]bool{3: true}, + }, + { + name: "all_errors", + bucketToRegion: map[string]string{"us-bucket-1": "us-east-1", "us-bucket-2": "us-east-1", "us-bucket-3": "us-east-1", "eu-bucket-1": "eu-west-1", "eu-bucket-2": "eu-west-1"}, + bucketCalls: []string{"us-bucket-1", "us-bucket-2", "us-bucket-3", "eu-bucket-1", "eu-bucket-2"}, + regionErrorsIndexes: map[int]bool{0: true, 1: true, 2: true, 3: true, 4: true}, + }, + { + name: "alternating_regions", + bucketToRegion: map[string]string{"us-bucket-1": "us-east-1", "us-bucket-2": "us-east-1", "us-bucket-3": "us-east-1", "eu-bucket-1": "eu-west-1", "eu-bucket-2": "eu-west-1"}, + bucketCalls: []string{"us-bucket-1", "eu-bucket-1", "us-bucket-2", "eu-bucket-2", "us-bucket-3", "us-bucket-1", "eu-bucket-1", "us-bucket-2", "eu-bucket-2", "us-bucket-3"}, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var callIdx int + var bucket string + actualClientsCreated := make(map[string]bool) + expectedClientsCreated := make(map[string]bool) + actualRegionFetch := make(map[string]bool) + expectedRegionFetch := make(map[string]bool) + + c := s3.NewClientCache(cfg, params.S3{}) // params are ignored as we use custom client factory + + c.SetClientFactory(func(region string) *awss3.Client { + if actualClientsCreated[region] { + t.Fatalf("client created more than once for a region") + } + actualClientsCreated[region] = true + return awss3.NewFromConfig(cfg, func(o *awss3.Options) { + o.Region = region + }) + }) + + c.SetS3RegionGetter(func(ctx context.Context, bucket string) (string, error) { + if actualRegionFetch[bucket] { + t.Fatalf("region fetched more than once for bucket") + } + actualRegionFetch[bucket] = true + if test.regionErrorsIndexes[callIdx] { + return "", errRegion + } + return test.bucketToRegion[bucket], nil + }) + + alreadyCalled := make(map[string]bool) + for callIdx, bucket = range test.bucketCalls { + expectedRegionFetch[bucket] = true // for every bucket, there should be exactly one region fetch + if _, ok := alreadyCalled[bucket]; !ok { + if test.regionErrorsIndexes[callIdx] { + // if there's an error, a client should be created for the default region + expectedClientsCreated[defaultRegion] = true + } else { + // for every region, a client is created exactly once + expectedClientsCreated[test.bucketToRegion[bucket]] = true + } + } + alreadyCalled[bucket] = true + c.Get(ctx, bucket) + } + if diff := deep.Equal(expectedClientsCreated, actualClientsCreated); diff != nil { + t.Fatal("unexpected client creation count: ", diff) + } + if diff := deep.Equal(expectedRegionFetch, actualRegionFetch); diff != nil { + t.Fatal("unexpected region fetch count. diff: ", diff) + } + }) + } +} diff --git a/block/s3/extract_sse.go b/block/s3/extract_sse.go new file mode 100644 index 00000000..dc0169dc --- /dev/null +++ b/block/s3/extract_sse.go @@ -0,0 +1,84 @@ +package s3 + +import ( + "net/http" + + "github.com/aws/aws-sdk-go-v2/service/s3" +) + +// extractSSHeaderUploadPart extracts the x-amz-server-side-* headers from the given +// UploadPartOutput response. +func extractSSHeaderUploadPart(resp *s3.UploadPartOutput) http.Header { + // x-amz-server-side-* headers + headers := make(http.Header) + if resp.SSECustomerAlgorithm != nil { + headers.Set("X-Amz-Server-Side-Encryption-Customer-Algorithm", *resp.SSECustomerAlgorithm) + } + if resp.SSECustomerKeyMD5 != nil { + headers.Set("X-Amz-Server-Side-Encryption-Customer-Key-Md5", *resp.SSECustomerKeyMD5) + } + if resp.SSEKMSKeyId != nil { + headers.Set("X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id", *resp.SSEKMSKeyId) + } + if resp.ServerSideEncryption != "" { + headers.Set("X-Amz-Server-Side-Encryption", string(resp.ServerSideEncryption)) + } + return headers +} + +// extractSSHeaderUploadPartCopy extracts the x-amz-server-side-* headers from the given +// UploadPartCopyOutput response. +func extractSSHeaderUploadPartCopy(resp *s3.UploadPartCopyOutput) http.Header { + // x-amz-server-side-* headers + headers := make(http.Header) + if resp.SSECustomerAlgorithm != nil { + headers.Set("X-Amz-Server-Side-Encryption-Customer-Algorithm", *resp.SSECustomerAlgorithm) + } + if resp.SSECustomerKeyMD5 != nil { + headers.Set("X-Amz-Server-Side-Encryption-Customer-Key-Md5", *resp.SSECustomerKeyMD5) + } + if resp.SSEKMSKeyId != nil { + headers.Set("X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id", *resp.SSEKMSKeyId) + } + if resp.ServerSideEncryption != "" { + headers.Set("X-Amz-Server-Side-Encryption", string(resp.ServerSideEncryption)) + } + return headers +} + +// extractSSHeaderCreateMultipartUpload extracts the x-amz-server-side-* headers from the given +// CreateMultipartUploadOutput response. +func extractSSHeaderCreateMultipartUpload(resp *s3.CreateMultipartUploadOutput) http.Header { + // x-amz-server-side-* headers + headers := make(http.Header) + if resp.SSECustomerAlgorithm != nil { + headers.Set("X-Amz-Server-Side-Encryption-Customer-Algorithm", *resp.SSECustomerAlgorithm) + } + if resp.SSECustomerKeyMD5 != nil { + headers.Set("X-Amz-Server-Side-Encryption-Customer-Key-Md5", *resp.SSECustomerKeyMD5) + } + if resp.SSEKMSKeyId != nil { + headers.Set("X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id", *resp.SSEKMSKeyId) + } + if resp.ServerSideEncryption != "" { + headers.Set("X-Amz-Server-Side-Encryption", string(resp.ServerSideEncryption)) + } + if resp.SSEKMSEncryptionContext != nil { + headers.Set("X-Amz-Server-Side-Encryption-Context", *resp.SSEKMSEncryptionContext) + } + return headers +} + +// extractSSHeaderCompleteMultipartUpload extracts the x-amz-server-side-* headers from the given +// CompleteMultipartUploadOutput response. +func extractSSHeaderCompleteMultipartUpload(resp *s3.CompleteMultipartUploadOutput) http.Header { + // x-amz-server-side-* headers + headers := make(http.Header) + if resp.SSEKMSKeyId != nil { + headers.Set("X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id", *resp.SSEKMSKeyId) + } + if resp.ServerSideEncryption != "" { + headers.Set("X-Amz-Server-Side-Encryption", string(resp.ServerSideEncryption)) + } + return headers +} diff --git a/block/s3/main_test.go b/block/s3/main_test.go new file mode 100644 index 00000000..8fd91a5f --- /dev/null +++ b/block/s3/main_test.go @@ -0,0 +1,86 @@ +package s3_test + +import ( + "context" + "fmt" + "log" + "os" + "testing" + + "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" + "github.com/ory/dockertest/v3" +) + +const ( + minioContainerTimeoutSeconds = 10 * 60 // 10 min + bucketName = "bucket1" + minioTestEndpoint = "127.0.0.1" + minioTestAccessKeyID = "Q3AM3UQ867SPQQA43P2F" + minioTestSecretAccessKey = "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG" +) + +var ( + blockURL string + pool *dockertest.Pool +) + +func newClient(port string) (*minio.Client, error) { + creds := credentials.NewStaticV4(minioTestAccessKeyID, minioTestSecretAccessKey, "") + return minio.New(fmt.Sprintf("%s:%s", minioTestEndpoint, port), &minio.Options{Creds: creds}) +} + +func TestMain(m *testing.M) { + var err error + pool, err = dockertest.NewPool("") + if err != nil { + log.Fatalf("Could not connect to Docker: %s", err) + } + resource, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "minio/minio", + Tag: "RELEASE.2023-06-09T07-32-12Z", + Env: []string{ + fmt.Sprintf("MINIO_ROOT_USER=%s", minioTestAccessKeyID), + fmt.Sprintf("MINIO_ROOT_PASSWORD=%s", minioTestSecretAccessKey), + }, + Cmd: []string{ + "server", + "start", + }, + }) + if err != nil { + panic(err) + } + + // set cleanup + closer := func() { + err := pool.Purge(resource) + if err != nil { + panic("could not purge minio container: " + err.Error()) + } + } + + // expire, just to make sure + err = resource.Expire(minioContainerTimeoutSeconds) + if err != nil { + panic("could not expire minio container: " + err.Error()) + } + + // Create a test client and bucket + client, err := newClient(resource.GetPort("9000/tcp")) + if err != nil { + log.Fatalf("create client: %s", err) + } + blockURL = client.EndpointURL().String() + + err = client.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{ + Region: "us-east-1", + }) + if err != nil { + log.Fatalf("create bucket: %s", err) + } + + code := m.Run() + closer() + os.Exit(code) +} diff --git a/block/s3/stats.go b/block/s3/stats.go new file mode 100644 index 00000000..571a1ba3 --- /dev/null +++ b/block/s3/stats.go @@ -0,0 +1,31 @@ +package s3 + +import ( + "strconv" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +var durationHistograms = promauto.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "s3_operation_duration_seconds", + Help: "durations of outgoing s3 operations", + }, + []string{"operation", "error"}) + +var requestSizeHistograms = promauto.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "s3_operation_size_bytes", + Help: "handled sizes of outgoing s3 operations", + Buckets: prometheus.ExponentialBuckets(1, 10, 10), //nolint: gomnd + }, []string{"operation", "error"}) + +func reportMetrics(operation string, start time.Time, sizeBytes *int64, err *error) { + isErrStr := strconv.FormatBool(*err != nil) + durationHistograms.WithLabelValues(operation, isErrStr).Observe(time.Since(start).Seconds()) + if sizeBytes != nil { + requestSizeHistograms.WithLabelValues(operation, isErrStr).Observe(float64(*sizeBytes)) + } +} diff --git a/block/s3/testdata/chunk250_data500.input b/block/s3/testdata/chunk250_data500.input new file mode 100755 index 00000000..3e858b7d --- /dev/null +++ b/block/s3/testdata/chunk250_data500.input @@ -0,0 +1 @@ +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA \ No newline at end of file diff --git a/block/s3/testdata/chunk250_data500.output b/block/s3/testdata/chunk250_data500.output new file mode 100755 index 00000000..f3e8104e --- /dev/null +++ b/block/s3/testdata/chunk250_data500.output @@ -0,0 +1,6 @@ +fa;chunk-signature=4ffd0dae90275680909132c7a3dc53619e81f5b80b0c6a3a37d1f4c1645ff9ba +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +fa;chunk-signature=88a17fee80f2b96bed975fae93cc365aec4d4a5ee99c7ec3423f688a4c752b7e +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +0;chunk-signature=e35c492bbd038bca16cf479b3cf20ae206969e66ee75cae87a590cf697723504 + diff --git a/block/s3/testdata/chunk250_data510.input b/block/s3/testdata/chunk250_data510.input new file mode 100755 index 00000000..44e098b2 --- /dev/null +++ b/block/s3/testdata/chunk250_data510.input @@ -0,0 +1 @@ +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA \ No newline at end of file diff --git a/block/s3/testdata/chunk250_data510.output b/block/s3/testdata/chunk250_data510.output new file mode 100755 index 00000000..c0faf0e2 --- /dev/null +++ b/block/s3/testdata/chunk250_data510.output @@ -0,0 +1,8 @@ +fa;chunk-signature=38734a4c4d2c4be07584b860262067890ea7ff8ddd90a75956484bc3988b2c08 +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +fa;chunk-signature=0b005f753b4247ca45b1005da539ad6264792219cfefa011018295df70a4f82a +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +a;chunk-signature=4b888cb2aa55e61e204229e384cce455af2439fe7a9e8f82880a6e4f6104a715 +AAAAAAAAAA +0;chunk-signature=62ef81f37a6656d2d4d45eded4c77efaeacd0e92a1ca7a1acc4ecfe6e0670685 + diff --git a/block/s3/testdata/chunk3000_data10.input b/block/s3/testdata/chunk3000_data10.input new file mode 100755 index 00000000..83cfb41e --- /dev/null +++ b/block/s3/testdata/chunk3000_data10.input @@ -0,0 +1 @@ +AAAAAAAAAA \ No newline at end of file diff --git a/block/s3/testdata/chunk3000_data10.output b/block/s3/testdata/chunk3000_data10.output new file mode 100755 index 00000000..aa0a7141 --- /dev/null +++ b/block/s3/testdata/chunk3000_data10.output @@ -0,0 +1,4 @@ +a;chunk-signature=82b3811727818699ea3bfcd68a8fd8c011c9dd72d4e04560b920440b166512c2 +AAAAAAAAAA +0;chunk-signature=966f15e070055c94efba15867341775173656745331cf57b7e5a54dd3a49a94b + diff --git a/block/s3/testdata/chunk5_data0.input b/block/s3/testdata/chunk5_data0.input new file mode 100755 index 00000000..e69de29b diff --git a/block/s3/testdata/chunk5_data0.output b/block/s3/testdata/chunk5_data0.output new file mode 100755 index 00000000..7a4394ba --- /dev/null +++ b/block/s3/testdata/chunk5_data0.output @@ -0,0 +1,2 @@ +0;chunk-signature=6533eceb4501215c93de7db8623da836c1d3308a00dada9f171ba3066aba782e + diff --git a/block/s3/testdata/chunk5_data10.input b/block/s3/testdata/chunk5_data10.input new file mode 100755 index 00000000..83cfb41e --- /dev/null +++ b/block/s3/testdata/chunk5_data10.input @@ -0,0 +1 @@ +AAAAAAAAAA \ No newline at end of file diff --git a/block/s3/testdata/chunk5_data10.output b/block/s3/testdata/chunk5_data10.output new file mode 100755 index 00000000..37f79158 --- /dev/null +++ b/block/s3/testdata/chunk5_data10.output @@ -0,0 +1,6 @@ +5;chunk-signature=688f3581d7837cc4bd0bf9b6c08685fbc4c8ff8c3f97d66ae9d947f50560a116 +AAAAA +5;chunk-signature=b931a89dff2f10bf1e825e6619dd675cc97648b1bb79100ff2477f91291aa34f +AAAAA +0;chunk-signature=c48cf0da01fe0ba5ce242ac82354b44223f7235455f2191bb4b29f2e28bfbe46 + diff --git a/block/s3/testdata/chunk600_data240.input b/block/s3/testdata/chunk600_data240.input new file mode 100755 index 00000000..ec3e9248 --- /dev/null +++ b/block/s3/testdata/chunk600_data240.input @@ -0,0 +1 @@ +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA \ No newline at end of file diff --git a/block/s3/testdata/chunk600_data240.output b/block/s3/testdata/chunk600_data240.output new file mode 100755 index 00000000..5cea9aaa --- /dev/null +++ b/block/s3/testdata/chunk600_data240.output @@ -0,0 +1,4 @@ +f0;chunk-signature=8f8478f8eb60b9c93164b7ed36b408f587d09b90bcde4b1081572488344f1f28 +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +0;chunk-signature=ee8146fb5aca93e210892961e513d7e003402ea7e89722bdfbd7a9a88ec03ce1 + diff --git a/block/s3/walker.go b/block/s3/walker.go new file mode 100644 index 00000000..5a5de76d --- /dev/null +++ b/block/s3/walker.go @@ -0,0 +1,97 @@ +package s3 + +import ( + "context" + "fmt" + "net/url" + "strings" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/jiaozifs/jiaozifs/block" +) + +type Walker struct { + client *s3.Client + mark block.Mark +} + +func NewS3Walker(client *s3.Client) *Walker { + return &Walker{ + client: client, + mark: block.Mark{HasMore: true}, + } +} + +func (s *Walker) Walk(ctx context.Context, storageURI *url.URL, op block.WalkOptions, walkFn func(e block.ObjectStoreEntry) error) error { + var continuation *string + const maxKeys = 1000 + prefix := strings.TrimLeft(storageURI.Path, "/") + + // basePath is the path relative to which the walk is done. The key of the resulting entries will be relative to this path. + // As the original prefix might not end with a separator, it cannot be used for the + // trim purpose, as this will create partial "folder" names. When the basePath is + // trimmed from the key, the remains will be the object name. + // Example: + // Say we have the following keys: + // pref/object + // pref/obj/another + // If we specify prefix="pref/obj" (both keys will be listed) then basePath="pref/" and the trim result + // for the keys will be: + // object + // obj/another + var basePath string + if idx := strings.LastIndex(prefix, "/"); idx != -1 { + basePath = prefix[:idx+1] + } + bucket := storageURI.Host + for { + result, err := s.client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ + Bucket: aws.String(bucket), + ContinuationToken: continuation, + MaxKeys: aws.Int32(maxKeys), + Prefix: aws.String(prefix), + StartAfter: aws.String(op.After), + }) + if continuation != nil { + s.mark.ContinuationToken = *continuation + } + if err != nil { + return err + } + for _, record := range result.Contents { + key := aws.ToString(record.Key) + addr := fmt.Sprintf("s3://%s/%s", bucket, key) + ent := block.ObjectStoreEntry{ + FullKey: key, + RelativeKey: strings.TrimPrefix(key, basePath), + Address: addr, + ETag: strings.Trim(aws.ToString(record.ETag), "\""), + Mtime: aws.ToTime(record.LastModified), + Size: *record.Size, + } + s.mark.LastKey = key + err := walkFn(ent) + if err != nil { + return err + } + } + if !*result.IsTruncated { //todo maybe panic need to know more about this field + break + } + continuation = result.NextContinuationToken + } + s.mark = block.Mark{ + LastKey: "", + HasMore: false, + } + return nil +} + +func (s *Walker) Marker() block.Mark { + return s.mark +} + +func (s *Walker) GetSkippedEntries() []block.ObjectStoreEntry { + return nil +} diff --git a/block/transient/adapter.go b/block/transient/adapter.go new file mode 100644 index 00000000..090bf0a2 --- /dev/null +++ b/block/transient/adapter.go @@ -0,0 +1,160 @@ +package transient + +import ( + "context" + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "io" + "net/http" + "net/url" + "time" + + "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/block" +) + +type Adapter struct{} + +func New(_ context.Context) *Adapter { + return &Adapter{} +} + +func (a *Adapter) Put(_ context.Context, _ block.ObjectPointer, _ int64, reader io.Reader, _ block.PutOpts) error { + _, err := io.Copy(io.Discard, reader) + return err +} + +func (a *Adapter) Get(_ context.Context, _ block.ObjectPointer, expectedSize int64) (io.ReadCloser, error) { + if expectedSize < 0 { + return nil, io.ErrUnexpectedEOF + } + return io.NopCloser(&io.LimitedReader{R: rand.Reader, N: expectedSize}), nil +} + +func (a *Adapter) GetWalker(_ *url.URL) (block.Walker, error) { + return nil, block.ErrOperationNotSupported +} + +func (a *Adapter) GetPreSignedURL(_ context.Context, _ block.ObjectPointer, _ block.PreSignMode) (string, time.Time, error) { + return "", time.Time{}, block.ErrOperationNotSupported +} + +func (a *Adapter) Exists(_ context.Context, _ block.ObjectPointer) (bool, error) { + return true, nil +} + +func (a *Adapter) GetRange(_ context.Context, _ block.ObjectPointer, startPosition int64, endPosition int64) (io.ReadCloser, error) { + n := endPosition - startPosition + if n < 0 { + return nil, io.ErrUnexpectedEOF + } + reader := &io.LimitedReader{ + R: rand.Reader, + N: n, + } + return io.NopCloser(reader), nil +} + +func (a *Adapter) GetProperties(_ context.Context, _ block.ObjectPointer) (block.Properties, error) { + return block.Properties{}, nil +} + +func (a *Adapter) Remove(_ context.Context, _ block.ObjectPointer) error { + return nil +} + +func (a *Adapter) Copy(_ context.Context, _, _ block.ObjectPointer) error { + return nil +} + +func (a *Adapter) UploadCopyPart(_ context.Context, _, _ block.ObjectPointer, _ string, _ int) (*block.UploadPartResponse, error) { + h := sha256.New() + code := h.Sum(nil) + etag := hex.EncodeToString(code) + return &block.UploadPartResponse{ + ETag: etag, + }, nil +} + +func (a *Adapter) UploadCopyPartRange(_ context.Context, _, _ block.ObjectPointer, _ string, _ int, startPosition, endPosition int64) (*block.UploadPartResponse, error) { + n := endPosition - startPosition + if n < 0 { + return nil, io.ErrUnexpectedEOF + } + h := sha256.New() + code := h.Sum(nil) + etag := hex.EncodeToString(code) + return &block.UploadPartResponse{ + ETag: etag, + }, nil +} + +func (a *Adapter) CreateMultiPartUpload(_ context.Context, _ block.ObjectPointer, _ *http.Request, _ block.CreateMultiPartUploadOpts) (*block.CreateMultiPartUploadResponse, error) { + uid := uuid.New() + uploadID := hex.EncodeToString(uid[:]) + return &block.CreateMultiPartUploadResponse{ + UploadID: uploadID, + }, nil +} + +func (a *Adapter) UploadPart(_ context.Context, _ block.ObjectPointer, _ int64, reader io.Reader, _ string, _ int) (*block.UploadPartResponse, error) { + data, err := io.ReadAll(reader) + if err != nil { + return nil, err + } + h := sha256.New() + _, err = h.Write(data) + if err != nil { + return nil, err + } + code := h.Sum(nil) + etag := hex.EncodeToString(code) + return &block.UploadPartResponse{ + ETag: etag, + }, nil +} + +func (a *Adapter) AbortMultiPartUpload(context.Context, block.ObjectPointer, string) error { + return nil +} + +func (a *Adapter) CompleteMultiPartUpload(context.Context, block.ObjectPointer, string, *block.MultipartUploadCompletion) (*block.CompleteMultiPartUploadResponse, error) { + const dataSize = 1024 + data := make([]byte, dataSize) + if _, err := rand.Read(data); err != nil { + return nil, err + } + + h := sha256.New() + _, err := h.Write(data) + if err != nil { + return nil, err + } + code := h.Sum(nil) + codeHex := hex.EncodeToString(code) + return &block.CompleteMultiPartUploadResponse{ + ETag: codeHex, + ContentLength: dataSize, + }, nil +} + +func (a *Adapter) BlockstoreType() string { + return block.BlockstoreTypeTransient +} + +func (a *Adapter) GetStorageNamespaceInfo() block.StorageNamespaceInfo { + info := block.DefaultStorageNamespaceInfo(block.BlockstoreTypeTransient) + info.PreSignSupport = false + info.PreSignSupportUI = false + info.ImportSupport = false + return info +} + +func (a *Adapter) ResolveNamespace(storageNamespace, key string, identifierType block.IdentifierType) (block.QualifiedKey, error) { + return block.DefaultResolveNamespace(storageNamespace, key, identifierType) +} + +func (a *Adapter) RuntimeStats() map[string]string { + return nil +} diff --git a/block/walker.go b/block/walker.go new file mode 100644 index 00000000..59588657 --- /dev/null +++ b/block/walker.go @@ -0,0 +1,79 @@ +package block + +import ( + "context" + "fmt" + "net/url" + "time" +) + +type Walker interface { + Walk(ctx context.Context, storageURI *url.URL, op WalkOptions, walkFn func(e ObjectStoreEntry) error) error + Marker() Mark + GetSkippedEntries() []ObjectStoreEntry +} + +type ObjectStoreEntry struct { + // FullKey represents the fully qualified path in the object store namespace for the given entry + FullKey string `json:"full_key,omitempty"` + // RelativeKey represents a path relative to prefix (or directory). If none specified, will be identical to FullKey + RelativeKey string `json:"relative_key,omitempty"` + // Address is a full URI for the entry, including the storage namespace (i.e. s3://bucket/path/to/key) + Address string `json:"address,omitempty"` + // ETag represents a hash of the entry's content. Generally as hex encoded MD5, + // but depends on the underlying object store + ETag string `json:"etag,omitempty"` + // Mtime is the last-modified datetime of the entry + Mtime time.Time `json:"mtime,omitempty"` + // Size in bytes + Size int64 `json:"size"` +} + +type WalkOptions struct { + // After - all walked items must be greater than After + After string + + // ContinuationToken is passed to the client for efficient listing. Value is Opaque to the caller. + ContinuationToken string +} + +type WalkerOptions struct { + S3EndpointURL string + StorageURI string + SkipOutOfOrder bool +} + +type WalkerWrapper struct { + walker Walker + uri *url.URL +} + +func NewWrapper(walker Walker, uri *url.URL) *WalkerWrapper { + return &WalkerWrapper{ + walker: walker, + uri: uri, + } +} + +func (ww *WalkerWrapper) Walk(ctx context.Context, opts WalkOptions, walkFn func(e ObjectStoreEntry) error) error { + return ww.walker.Walk(ctx, ww.uri, opts, walkFn) +} + +func (ww *WalkerWrapper) Marker() Mark { + return ww.walker.Marker() +} + +func (ww *WalkerWrapper) GetSkippedEntries() []ObjectStoreEntry { + return ww.walker.GetSkippedEntries() +} + +type Mark struct { + ContinuationToken string + LastKey string + HasMore bool +} + +func (e ObjectStoreEntry) String() string { + return fmt.Sprintf("ObjectStoreEntry: {Address:%s, RelativeKey:%s, ETag:%s, Size:%d, Mtime:%s}", + e.Address, e.RelativeKey, e.ETag, e.Size, e.Mtime) +} diff --git a/cmd/daemon.go b/cmd/daemon.go index 556e1945..fbb6d5cb 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -3,6 +3,11 @@ package cmd import ( "context" + "github.com/jiaozifs/jiaozifs/block/params" + + "github.com/jiaozifs/jiaozifs/block" + "github.com/jiaozifs/jiaozifs/block/factory" + logging "github.com/ipfs/go-log/v2" apiImpl "github.com/jiaozifs/jiaozifs/api/api_impl" "github.com/jiaozifs/jiaozifs/config" @@ -41,6 +46,9 @@ var daemonCmd = &cobra.Command{ fx_opt.Override(new(*config.Config), cfg), fx_opt.Override(new(*config.APIConfig), &cfg.API), fx_opt.Override(new(*config.DatabaseConfig), &cfg.Database), + fx_opt.Override(new(params.AdapterConfig), &cfg.Blockstore), + //blockstore + fx_opt.Override(new(block.Adapter), factory.BuildBlockAdapter), //database fx_opt.Override(new(*bun.DB), models.SetupDatabase), fx_opt.Override(fx_opt.NextInvoke(), migrations.MigrateDatabase), diff --git a/cmd/init.go b/cmd/init.go index 3168af92..5b9786b4 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -1,7 +1,10 @@ package cmd import ( + "os" + "github.com/jiaozifs/jiaozifs/config" + "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -16,7 +19,16 @@ var initCmd = &cobra.Command{ return viper.BindPFlag("database.connection", cmd.Flags().Lookup("db")) }, RunE: func(cmd *cobra.Command, args []string) error { - return config.InitConfig() + err := config.InitConfig() + if err != nil { + return err + } + //create a blockstore in home path for default usage + defaultBsPath, err := homedir.Expand(config.DefaultLocalBSPath) + if err != nil { + return err + } + return os.MkdirAll(defaultBsPath, 0755) }, } diff --git a/config/blockstore.go b/config/blockstore.go new file mode 100644 index 00000000..704093cf --- /dev/null +++ b/config/blockstore.go @@ -0,0 +1,162 @@ +package config + +import ( + "fmt" + "time" + + "github.com/jiaozifs/jiaozifs/block/params" + + "github.com/mitchellh/go-homedir" +) + +type BlockStoreConfig struct { + Type string `mapstructure:"type" validate:"required"` + DefaultNamespacePrefix *string `mapstructure:"default_namespace_prefix"` + + Local *struct { + Path string `mapstructure:"path"` + ImportEnabled bool `mapstructure:"import_enabled"` + ImportHidden bool `mapstructure:"import_hidden"` + AllowedExternalPrefixes []string `mapstructure:"allowed_external_prefixes"` + } `mapstructure:"local"` + S3 *struct { + S3AuthInfo `mapstructure:",squash"` + Region string `mapstructure:"region"` + Endpoint string `mapstructure:"endpoint"` + MaxRetries int `mapstructure:"max_retries"` + ForcePathStyle bool `mapstructure:"force_path_style"` + DiscoverBucketRegion bool `mapstructure:"discover_bucket_region"` + SkipVerifyCertificateTestOnly bool `mapstructure:"skip_verify_certificate_test_only"` + ServerSideEncryption string `mapstructure:"server_side_encryption"` + ServerSideEncryptionKmsKeyID string `mapstructure:"server_side_encryption_kms_key_id"` + PreSignedExpiry time.Duration `mapstructure:"pre_signed_expiry"` + DisablePreSigned bool `mapstructure:"disable_pre_signed"` + DisablePreSignedUI bool `mapstructure:"disable_pre_signed_ui"` + ClientLogRetries bool `mapstructure:"client_log_retries"` + ClientLogRequest bool `mapstructure:"client_log_request"` + WebIdentity *struct { + SessionDuration time.Duration `mapstructure:"session_duration"` + SessionExpiryWindow time.Duration `mapstructure:"session_expiry_window"` + } `mapstructure:"web_identity"` + } `mapstructure:"s3"` + Azure *struct { + TryTimeout time.Duration `mapstructure:"try_timeout"` + StorageAccount string `mapstructure:"storage_account"` + StorageAccessKey string `mapstructure:"storage_access_key"` + PreSignedExpiry time.Duration `mapstructure:"pre_signed_expiry"` + DisablePreSigned bool `mapstructure:"disable_pre_signed"` + DisablePreSignedUI bool `mapstructure:"disable_pre_signed_ui"` + // TestEndpointURL for testing purposes + TestEndpointURL string `mapstructure:"test_endpoint_url"` + } `mapstructure:"azure"` + GS *struct { + S3Endpoint string `mapstructure:"s3_endpoint"` + CredentialsFile string `mapstructure:"credentials_file"` + CredentialsJSON string `mapstructure:"credentials_json"` + PreSignedExpiry time.Duration `mapstructure:"pre_signed_expiry"` + DisablePreSigned bool `mapstructure:"disable_pre_signed"` + DisablePreSignedUI bool `mapstructure:"disable_pre_signed_ui"` + } `mapstructure:"gs"` +} + +func (c *BlockStoreConfig) BlockstoreType() string { + return c.Type +} + +func (c *BlockStoreConfig) BlockstoreS3Params() (params.S3, error) { + var webIdentity *params.S3WebIdentity + if c.S3.WebIdentity != nil { + webIdentity = ¶ms.S3WebIdentity{ + SessionDuration: c.S3.WebIdentity.SessionDuration, + SessionExpiryWindow: c.S3.WebIdentity.SessionExpiryWindow, + } + } + + var creds params.S3Credentials + if c.S3.Credentials != nil { + creds.AccessKeyID = c.S3.Credentials.AccessKeyID.SecureValue() + creds.SecretAccessKey = c.S3.Credentials.SecretAccessKey.SecureValue() + creds.SessionToken = c.S3.Credentials.SessionToken.SecureValue() + } + + return params.S3{ + Region: c.S3.Region, + Profile: c.S3.Profile, + CredentialsFile: c.S3.CredentialsFile, + Credentials: creds, + MaxRetries: c.S3.MaxRetries, + Endpoint: c.S3.Endpoint, + ForcePathStyle: c.S3.ForcePathStyle, + DiscoverBucketRegion: c.S3.DiscoverBucketRegion, + SkipVerifyCertificateTestOnly: c.S3.SkipVerifyCertificateTestOnly, + ServerSideEncryption: c.S3.ServerSideEncryption, + ServerSideEncryptionKmsKeyID: c.S3.ServerSideEncryptionKmsKeyID, + PreSignedExpiry: c.S3.PreSignedExpiry, + DisablePreSigned: c.S3.DisablePreSigned, + DisablePreSignedUI: c.S3.DisablePreSignedUI, + ClientLogRetries: c.S3.ClientLogRetries, + ClientLogRequest: c.S3.ClientLogRequest, + WebIdentity: webIdentity, + }, nil +} + +func (c *BlockStoreConfig) BlockstoreLocalParams() (params.Local, error) { + localPath := c.Local.Path + path, err := homedir.Expand(localPath) + if err != nil { + return params.Local{}, fmt.Errorf("parse blockstore location URI %s: %w", localPath, err) + } + + params := params.Local(*c.Local) + params.Path = path + return params, nil +} + +func (c *BlockStoreConfig) BlockstoreGSParams() (params.GS, error) { + credPath, err := homedir.Expand(c.GS.CredentialsFile) + if err != nil { + return params.GS{}, fmt.Errorf("parse GS credentials path '%s': %w", c.GS.CredentialsFile, err) + } + return params.GS{ + CredentialsFile: credPath, + CredentialsJSON: c.GS.CredentialsJSON, + PreSignedExpiry: c.GS.PreSignedExpiry, + DisablePreSigned: c.GS.DisablePreSigned, + DisablePreSignedUI: c.GS.DisablePreSignedUI, + }, nil +} + +func (c *BlockStoreConfig) BlockstoreAzureParams() (params.Azure, error) { + return params.Azure{ + StorageAccount: c.Azure.StorageAccount, + StorageAccessKey: c.Azure.StorageAccessKey, + TryTimeout: c.Azure.TryTimeout, + PreSignedExpiry: c.Azure.PreSignedExpiry, + TestEndpointURL: c.Azure.TestEndpointURL, + DisablePreSigned: c.Azure.DisablePreSigned, + DisablePreSignedUI: c.Azure.DisablePreSignedUI, + }, nil +} + +type SecureString string + +// String returns an elided version. It is safe to call for logging. +func (SecureString) String() string { + return "[SECRET]" +} + +// SecureValue returns the actual value of s as a string. +func (s SecureString) SecureValue() string { + return string(s) +} + +// S3AuthInfo holds S3-style authentication. +type S3AuthInfo struct { + CredentialsFile string `mapstructure:"credentials_file"` + Profile string + Credentials *struct { + AccessKeyID SecureString `mapstructure:"access_key_id"` + SecretAccessKey SecureString `mapstructure:"secret_access_key"` + SessionToken SecureString `mapstructure:"session_token"` + } +} diff --git a/config/config.go b/config/config.go index 423619e9..0f034e2c 100644 --- a/config/config.go +++ b/config/config.go @@ -2,7 +2,6 @@ package config import ( "fmt" - "os" "path" @@ -16,6 +15,8 @@ type Config struct { Log LogConfig `mapstructure:"log"` API APIConfig `mapstructure:"api"` Database DatabaseConfig `mapstructure:"database"` + + Blockstore BlockStoreConfig `mapstructure:"blockstore"` } type LogConfig struct { @@ -50,7 +51,7 @@ func InitConfig() error { for k, v := range data { viper.SetDefault(k, v) } - err = os.MkdirAll(jiaoziHome, 0777) + err = os.MkdirAll(jiaoziHome, 0755) if err != nil { return err } diff --git a/config/default.go b/config/default.go index bfbb3263..47d6d8bf 100644 --- a/config/default.go +++ b/config/default.go @@ -1,5 +1,7 @@ package config +var DefaultLocalBSPath = "~/.jiaozifs/blockstore" + var defaultCfg = Config{ Path: "~/.jiaozifs/config.toml", Log: LogConfig{ @@ -8,4 +10,19 @@ var defaultCfg = Config{ API: APIConfig{ Listen: "http://127.0.0.1:34913", }, + Blockstore: BlockStoreConfig{ + Type: "local", + DefaultNamespacePrefix: nil, + Local: (*struct { + Path string `mapstructure:"path"` + ImportEnabled bool `mapstructure:"import_enabled"` + ImportHidden bool `mapstructure:"import_hidden"` + AllowedExternalPrefixes []string `mapstructure:"allowed_external_prefixes"` + })(&struct { + Path string + ImportEnabled bool + ImportHidden bool + AllowedExternalPrefixes []string + }{Path: DefaultLocalBSPath, ImportEnabled: false, ImportHidden: false, AllowedExternalPrefixes: nil}), + }, } diff --git a/go.mod b/go.mod index 6fba1d74..519d3f4b 100644 --- a/go.mod +++ b/go.mod @@ -3,71 +3,167 @@ module github.com/jiaozifs/jiaozifs go 1.20 require ( + cloud.google.com/go/storage v1.33.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 + github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0 + github.com/aws/aws-sdk-go-v2 v1.23.4 + github.com/aws/aws-sdk-go-v2/config v1.25.10 + github.com/aws/aws-sdk-go-v2/credentials v1.16.8 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.3 + github.com/aws/aws-sdk-go-v2/service/s3 v1.47.1 + github.com/aws/smithy-go v1.18.1 + github.com/benburkert/dns v0.0.0-20190225204957-d356cf78cdfc + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/deepmap/oapi-codegen/v2 v2.0.1-0.20231120160225-add3126ee845 + github.com/fergusstrange/embedded-postgres v1.25.0 github.com/getkin/kin-openapi v0.118.0 github.com/go-chi/chi/v5 v5.0.10 + github.com/go-chi/cors v1.2.1 + github.com/go-test/deep v1.1.0 + github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.4.0 + github.com/hnlq715/golang-lru v0.4.0 github.com/ipfs/go-log/v2 v2.5.1 + github.com/matoous/go-nanoid/v2 v2.0.0 + github.com/minio/minio-go/v7 v7.0.64 + github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/mapstructure v1.5.0 github.com/oapi-codegen/nethttp-middleware v1.0.1 + github.com/ory/dockertest/v3 v3.10.0 + github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 + github.com/pkg/errors v0.9.1 + github.com/prometheus/client_golang v1.17.0 + github.com/puzpuzpuz/xsync v1.5.2 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.17.0 + github.com/stretchr/testify v1.8.4 + github.com/thanhpk/randstr v1.0.6 github.com/uptrace/bun v1.1.16 github.com/uptrace/bun/dialect/pgdialect v1.1.16 github.com/uptrace/bun/driver/pgdriver v1.1.16 + github.com/uptrace/bun/extra/bundebug v1.1.16 go.uber.org/fx v1.20.1 + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 + golang.org/x/oauth2 v0.13.0 + google.golang.org/api v0.147.0 gopkg.in/yaml.v2 v2.4.0 ) require ( - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + cloud.google.com/go v0.110.8 // indirect + cloud.google.com/go/compute v1.23.0 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/iam v1.1.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.3 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.8 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.7 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.7 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.18.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.26.1 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/containerd/continuity v0.3.0 // indirect + github.com/docker/cli v23.0.6+incompatible // indirect + github.com/docker/docker v23.0.6+incompatible // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/fatih/color v1.15.0 // indirect - github.com/fergusstrange/embedded-postgres v1.25.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-chi/cors v1.2.1 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/swag v0.22.4 // indirect - github.com/google/go-cmp v0.6.0 // indirect + github.com/go-sql-driver/mysql v1.7.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v5 v5.0.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/s2a-go v0.1.7 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect + github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/imdario/mergo v0.3.15 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/yaml v0.2.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.5 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/minio/md5-simd v1.1.2 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.0.2 // indirect + github.com/opencontainers/runc v1.1.7 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/perimeterx/marshmallow v1.1.4 // indirect - github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 // indirect + github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect + github.com/prometheus/common v0.44.0 // indirect + github.com/prometheus/procfs v0.11.1 // indirect + github.com/rs/xid v1.5.0 // indirect github.com/sagikazarmark/locafero v0.3.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.10.0 // indirect github.com/spf13/cast v1.5.1 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.5.0 // indirect - github.com/stretchr/testify v1.8.4 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect - github.com/uptrace/bun/extra/bundebug v1.1.16 // indirect github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect - go.uber.org/atomic v1.9.0 // indirect + go.opencensus.io v0.24.0 // indirect + go.uber.org/atomic v1.11.0 // indirect go.uber.org/dig v1.17.0 // indirect go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.23.0 // indirect - golang.org/x/crypto v0.13.0 // indirect - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/crypto v0.14.0 // indirect golang.org/x/mod v0.12.0 // indirect - golang.org/x/sys v0.12.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sync v0.4.0 // indirect + golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.13.0 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c // indirect + google.golang.org/grpc v1.58.3 // indirect + google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect mellium.im/sasl v0.3.1 // indirect diff --git a/go.sum b/go.sum index 40690bbe..27da620d 100644 --- a/go.sum +++ b/go.sum @@ -17,14 +17,22 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.110.8 h1:tyNdfIxjzaWctIiLYOTalaLKZ17SI44SKFW26QbOhME= +cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= +cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/iam v1.1.2 h1:gacbrBdWcoVmGLozRuStX45YKvJtzIjJdAolzUs1sm4= +cloud.google.com/go/iam v1.1.2/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -35,12 +43,77 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.33.0 h1:PVrDOkIC8qQVa1P3SXGpQvfuJhN2LHOoyZvWs8D2X5M= +cloud.google.com/go/storage v1.33.0/go.mod h1:Hhh/dogNRGca7IWv1RC2YqEn0c0G77ctA/OxflYkiD8= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0 h1:fb8kj/Dh4CSwgsOzHeZY4Xh68cFVbzXx+ONXGMY//4w= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0/go.mod h1:uReU2sSxZExRPBAg3qKzmAucSi51+SP1OhohieR821Q= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 h1:BMAjVKJM0U/CYF27gA0ZMmXGkOcvfFtD0oHVZ1TIPRI= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0/go.mod h1:1fXstnBMas5kzG+S3q8UoJcmyU6nUeunJcMDHcRYHhs= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0 h1:d81/ng9rET2YqdVkVwkb6EXeRrLJIwyGnJcAlAWKwhs= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0 h1:Ma67P/GGprNwsslzEH6+Kb8nybI8jpDTm4Wmzu2ReK8= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0 h1:gggzg0SUMs6SQbEw+3LoSsYf9YMjkupeAnHMX8O9mmY= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0/go.mod h1:+6KLcKIVgxoBDMqMO/Nvy7bZ9a0nbU3I1DtFQK3YvB4= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 h1:WpB/QDNLpMw72xHJc34BNNykqSOeEJDAWkhf0u12/Jk= +github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/aws/aws-sdk-go-v2 v1.23.4 h1:2P20ZjH0ouSAu/6yZep8oCmTReathLuEu6dwoqEgjts= +github.com/aws/aws-sdk-go-v2 v1.23.4/go.mod h1:t3szzKfP0NeRU27uBFczDivYJjsmSnqI8kIvKyWb9ds= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.3 h1:Zx9+31KyB8wQna6SXFWOewlgoY5uGdDAu6PTOEU3OQI= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.3/go.mod h1:zxbEJhRdKTH1nqS2qu6UJ7zGe25xaHxZXaC2CvuQFnA= +github.com/aws/aws-sdk-go-v2/config v1.25.10 h1:qw/e8emDtNufTkrAU86DlQ18DruMyyM7ttW6Lgwp4v0= +github.com/aws/aws-sdk-go-v2/config v1.25.10/go.mod h1:203YiAtb6XyoGxXMPsUVwEcuxCiTQY/r8P27IDjfvMc= +github.com/aws/aws-sdk-go-v2/credentials v1.16.8 h1:phw9nRLy/77bPk6Mfu2SHCOnHwfVB7WWrOa5rZIY2Fc= +github.com/aws/aws-sdk-go-v2/credentials v1.16.8/go.mod h1:MrS4SOin6adbO6wgWhdifyPiq+TX7fPPwyA/ZLC1F5M= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.8 h1:tQZLSPC2Zj2CqZHonLmWEvCsbpMX5tQvaYJWHadcPek= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.8/go.mod h1:5+YpvTHDFffykWr5qAGjqwoh8oVYZOddL3sSrEN7lws= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.3 h1:0Pw2ku539I0EugduMpJ+579WRc+38nv8rZhThWjsuYQ= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.3/go.mod h1:vQtGu6huTQkoEhNgkDeijtYm9Y8HgpQqvGeKUPoEunY= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.7 h1:eMqD7ku6WGdmcWWXPYun9m6yk6feSULLhJlAtN6rYG4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.7/go.mod h1:0oBIfcDV6LScxEW0VgOqxT3e4aqKRp+SYhB9wAd5E3Q= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.7 h1:+XYhWhgWs5F3Zx8oa49CXzNvfXrItaDjZB/M172fcHQ= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.7/go.mod h1:L6tcSRyCGxcKfDWUrmv2jv8G1cLDU7d0FUpEFpG9bVE= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 h1:uR9lXYjdPX0xY+NhvaJ4dD8rpSRz5VY81ccIIoNG+lw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.7 h1:3VaUNB1LclLomv82VnP5QnxAfowG+Ro4m82+af9wjZ4= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.7/go.mod h1:D5i0c+qvEY0LV5F4elFZd+mYnvHQbufCLHNHoBfQR2g= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.3 h1:e3PCNeEaev/ZF01cQyNZgmYE9oYYePIMJs2mWSKG514= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.3/go.mod h1:gIeeNyaL8tIEqZrzAnTeyhHcE0yysCtcaP+N9kxLZ+E= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.7 h1:Mft1tmIK1fkFS9l9sYVYiN+OdgXeOcQ9ZS3SxKOh3A4= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.7/go.mod h1:QWI83fhocxDaN3b74N8rrvET60CBaike5lQ+5sm3OcE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.7 h1:dU+ZyhvqMB/T/TxjGagHMCdyUiqaThRIaMu3YvKiSQI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.7/go.mod h1:SGORuNqoXyWfTvTp/gBGJfv8jRvW/+nha0XhnIXVI+o= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.7 h1:ybtGXm0qFVFi0hFUF7eFAVnL3ntl9MO7lrxhhGP7KYU= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.7/go.mod h1:BUyWJUKAnNqoEq1LfyQxy+Eh4U8Y3c5w2C6m21f3yvI= +github.com/aws/aws-sdk-go-v2/service/s3 v1.47.1 h1:0/W5F+LlXzKZ7KTsRcD8pugasVnsrjUWmhOsN/LdSFY= +github.com/aws/aws-sdk-go-v2/service/s3 v1.47.1/go.mod h1:TqThLn4bRCn/UYf960hNZgPPjmxc17fQcwmjfuG6D5k= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.1 h1:V40g2daNO3l1J94JYwqfkyvQMYXi5I25fs3fNQW8iDs= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.1/go.mod h1:0ZWQJP/mBOUxkCvZKybZNz1XmdUKSBxoF0dzgfxtvDs= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.1 h1:uQrj7SpUNC3r55vc1CDh3qV9wJC66lz546xM9dhSo5s= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.1/go.mod h1:oyaTk5xEAOuPXX1kCD7HmIeuLqdj3Bk5yGkqGXtGi14= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.1 h1:K33V7L0XDdb23FMOZySr8bon1jou5SHn1fiv7NJ1SUg= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.1/go.mod h1:YtXUl/sfnS06VksYhr855hTQf2HphfT1Xv/EwuzbPjg= +github.com/aws/smithy-go v1.18.1 h1:pOdBTUfXNazOlxLrgeYalVnuTpKreACHtc62xLwIB3c= +github.com/aws/smithy-go v1.18.1/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benburkert/dns v0.0.0-20190225204957-d356cf78cdfc h1:eyDlmf21vuKN61WoxV2cQLDH/PBDyyjIhUI4kT2o1yM= +github.com/benburkert/dns v0.0.0-20190225204957-d356cf78cdfc/go.mod h1:6ul4nJKqsreAIBK5lUkibcUn2YBU6CvDzlKDH+dtZsQ= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -48,14 +121,28 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= +github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deepmap/oapi-codegen/v2 v2.0.1-0.20231120160225-add3126ee845 h1:QlRkcr1t+VcHkdk8WJDhuiI94RqmjSgtflB3Q+H8X2k= github.com/deepmap/oapi-codegen/v2 v2.0.1-0.20231120160225-add3126ee845/go.mod h1:pB9cROTwrn6Gj3Rtmcmp5fwV23znquC9tY1rR6+/R3s= +github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= +github.com/docker/cli v23.0.6+incompatible h1:CScadyCJ2ZKUDpAMZta6vK8I+6/m60VIjGIV7Wg/Eu4= +github.com/docker/cli v23.0.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker v23.0.6+incompatible h1:aBD4np894vatVX99UTx/GyOUOK4uEcROwA3+bQhEcoU= +github.com/docker/docker v23.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -85,12 +172,21 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= +github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -112,6 +208,9 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -122,13 +221,17 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -140,11 +243,19 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.1 h1:SBWmZhjUDRorQxrN0nwzf+AHBxnbFjViHQS4P0yVpmQ= +github.com/googleapis/enterprise-certificate-proxy v0.3.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= @@ -152,8 +263,12 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hnlq715/golang-lru v0.4.0 h1:gyo/wIvLE6Upf1wucAfwTjpR+BQ5Lli2766H2MnNPv0= +github.com/hnlq715/golang-lru v0.4.0/go.mod h1:RBkgDAtlu0SgTPvpb4VW2/RQnkCBMRD3Lr6B9RhsAS8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= +github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= @@ -163,11 +278,23 @@ github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= +github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= +github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -176,6 +303,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -184,24 +313,54 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U= +github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL+9Tj0= +github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= +github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= +github.com/minio/minio-go/v7 v7.0.64 h1:Zdza8HwOzkld0ZG/og50w56fKi6AAyfqfifmasD9n2Q= +github.com/minio/minio-go/v7 v7.0.64/go.mod h1:R4WVUR6ZTedlCcGwZRauLMIKjgyaWxhs4Mqi/OMPmEc= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/oapi-codegen/nethttp-middleware v1.0.1 h1:ZWvwfnMU0eloHX1VEJmQscQm3741t0vCm0eSIie1NIo= github.com/oapi-codegen/nethttp-middleware v1.0.1/go.mod h1:P7xtAvpoqNB+5obR9qRCeefH7YlXWSK3KgPs/9WB8tE= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v1.1.7 h1:y2EZDS8sNng4Ksf0GUYNhKbTShZJPJg1FiXJNH/uoCk= +github.com/opencontainers/runc v1.1.7/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50= +github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= +github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55FuFkxqLw= github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -209,14 +368,28 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= +github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= +github.com/puzpuzpuz/xsync v1.5.2 h1:yRAP4wqSOZG+/4pxJ08fPTwrfL0IzE/LKQ/cw509qGY= +github.com/puzpuzpuz/xsync v1.5.2/go.mod h1:K98BYhX3k1dQ2M63t1YNVDanbwUPmBCAhNmVrrxfiGg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= @@ -231,7 +404,6 @@ github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -245,6 +417,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/thanhpk/randstr v1.0.6 h1:psAOktJFD4vV9NEVb3qkhRSMvYh4ORRaj1+w/hn4B+o= +github.com/thanhpk/randstr v1.0.6/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= @@ -263,6 +437,13 @@ github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9 github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -276,16 +457,16 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= go.uber.org/fx v1.20.1 h1:zVwVQGS8zYvhh9Xxcu4w1M6ESyeMzebzj2NbSayZ4Mk= go.uber.org/fx v1.20.1/go.mod h1:iSYNbHf2y55acNCwCXKx7LbWb5WG1Bnue5RDXz1OREg= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= @@ -300,8 +481,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -369,11 +550,14 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -383,6 +567,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= +golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -394,6 +580,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -431,12 +619,16 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -488,6 +680,7 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= @@ -496,6 +689,7 @@ golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= @@ -505,6 +699,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -524,12 +720,15 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.147.0 h1:Can3FaQo9LlVqxJCodNmeZW/ib3/qKAY3rFeXiHo5gc= +google.golang.org/api v0.147.0/go.mod h1:pQ/9j83DcmPd/5C9e2nFOdjjNkDZ1G+zkbK2uvdkJMs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -567,6 +766,12 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 h1:SeZZZx0cP0fqUyA+oRzP9k7cSwJlvDFiROO72uwD6i0= +google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk= +google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU= +google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c h1:jHkCUWkseRf+W+edG5hMzr/Uh1xkDREY4caybAq4dpY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c/go.mod h1:4cYg8o5yUbm77w8ZX00LhMVNl/YVBFJRYWDc0uYWMs0= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -583,6 +788,8 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= +google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -593,6 +800,10 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= @@ -609,6 +820,7 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 0c103b186faba198c3b059e438c0384f5550f60b Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Thu, 30 Nov 2023 15:45:35 +0800 Subject: [PATCH 026/210] fix: version command --- api/api_impl/server.go | 6 +----- cmd/helper.go | 20 ++++++++++++++++++++ cmd/version.go | 14 +++++--------- 3 files changed, 26 insertions(+), 14 deletions(-) create mode 100644 cmd/helper.go diff --git a/api/api_impl/server.go b/api/api_impl/server.go index 480e42d8..2868d01f 100644 --- a/api/api_impl/server.go +++ b/api/api_impl/server.go @@ -3,7 +3,6 @@ package apiimpl import ( "context" "errors" - "net" "net/http" @@ -29,9 +28,6 @@ func SetupAPI(lc fx.Lifecycle, apiConfig *config.APIConfig, controller APIContro return err } - // Clear out the servers array in the swagger spec, that skips validating - // that server names match. We don't know how this thing will be run. - swagger.Servers = nil // This is how you set up a basic chi router r := chi.NewRouter() @@ -58,11 +54,11 @@ func SetupAPI(lc fx.Lifecycle, apiConfig *config.APIConfig, controller APIContro return nil }, }, + SilenceServersWarning: true, }), ) api.HandlerFromMuxWithBaseURL(controller, r, APIV1Prefix) - url, err := url.Parse(apiConfig.Listen) if err != nil { return err diff --git a/cmd/helper.go b/cmd/helper.go new file mode 100644 index 00000000..080a3580 --- /dev/null +++ b/cmd/helper.go @@ -0,0 +1,20 @@ +package cmd + +import ( + "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/config" +) + +func GetDefaultClient() (*api.Client, error) { + swagger, err := api.GetSwagger() + if err != nil { + return nil, err + } + + //get runtime version + cfg, err := config.LoadConfig(cfgFile) + if err != nil { + return nil, err + } + return api.NewClient(cfg.API.Listen + swagger.Servers[0].URL) +} diff --git a/cmd/version.go b/cmd/version.go index 523361c0..5a36093d 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -3,10 +3,9 @@ package cmd import ( "fmt" - "github.com/jiaozifs/jiaozifs/api" - apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" - "github.com/jiaozifs/jiaozifs/config" "github.com/jiaozifs/jiaozifs/version" + + "github.com/jiaozifs/jiaozifs/api" "github.com/spf13/cobra" ) @@ -23,12 +22,7 @@ var versionCmd = &cobra.Command{ fmt.Println("Version ", version.UserVersion()) fmt.Println("API Version ", swagger.Info.Version) - //get runtime version - cfg, err := config.LoadConfig(cfgFile) - if err != nil { - return err - } - client, err := api.NewClient(cfg.API.Listen + apiimpl.APIV1Prefix) + client, err := GetDefaultClient() if err != nil { return err } @@ -37,10 +31,12 @@ var versionCmd = &cobra.Command{ if err != nil { return err } + okResp, err := api.ParseGetVersionResponse(versionResp) if err != nil { return err } + if okResp.JSON200 == nil { return fmt.Errorf("request version fail %d %s", okResp.HTTPResponse.StatusCode, okResp.HTTPResponse.Body) } From da30a94ec49b392416e533a7700a1388c4fecfb0 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Fri, 1 Dec 2023 14:52:36 +0800 Subject: [PATCH 027/210] feat: add basic type --- api/api_impl/impl.go | 16 + api/custom_response.go | 7 +- api/jiaozifs.gen.go | 1097 ++++++++++++++++- api/swagger.yml | 305 +++++ cmd/daemon.go | 4 + controller/object_ctl.go | 32 + .../common.go => controller/version_ctl.go | 8 +- go.mod | 8 + go.sum | 16 + models/filemode/filemode.go | 190 +++ models/filemode/filemode_test.go | 348 ++++++ .../migrations/20210505110026_init_project.go | 27 + models/object.go | 114 ++ models/object_test.go | 39 + models/ref.go | 54 + models/ref_test.go | 31 + models/repository.go | 50 + models/repository_test.go | 31 + models/user.go | 6 +- models/user_test.go | 19 +- utils/hash/hash.go | 62 + utils/hash/hash_sha1.go | 15 + utils/hash/hash_sha256.go | 15 + utils/hash/hash_test.go | 103 ++ 24 files changed, 2517 insertions(+), 80 deletions(-) create mode 100644 api/api_impl/impl.go create mode 100644 controller/object_ctl.go rename api/api_impl/common.go => controller/version_ctl.go (65%) create mode 100644 models/filemode/filemode.go create mode 100644 models/filemode/filemode_test.go create mode 100644 models/object.go create mode 100644 models/object_test.go create mode 100644 models/ref.go create mode 100644 models/ref_test.go create mode 100644 models/repository.go create mode 100644 models/repository_test.go create mode 100644 utils/hash/hash.go create mode 100644 utils/hash/hash_sha1.go create mode 100644 utils/hash/hash_sha256.go create mode 100644 utils/hash/hash_test.go diff --git a/api/api_impl/impl.go b/api/api_impl/impl.go new file mode 100644 index 00000000..1f759ae4 --- /dev/null +++ b/api/api_impl/impl.go @@ -0,0 +1,16 @@ +package apiimpl + +import ( + "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/controller" + "go.uber.org/fx" +) + +var _ api.ServerInterface = (*APIController)(nil) + +type APIController struct { + fx.In + + controller.VersionController + controller.ObjectController +} diff --git a/api/custom_response.go b/api/custom_response.go index 0823c893..9211856e 100644 --- a/api/custom_response.go +++ b/api/custom_response.go @@ -10,14 +10,13 @@ type JiaozifsResponse struct { } func (response *JiaozifsResponse) RespJSON(v interface{}) { - data, err := json.Marshal(v) + response.Header().Set("Content-Type", "application/json") + response.WriteHeader(http.StatusOK) + err := json.NewEncoder(response).Encode(v) if err != nil { response.RespError(err) return } - response.Header().Set("Content-Type", "application/json") - response.WriteHeader(http.StatusOK) - _, _ = response.Write(data) } func (response *JiaozifsResponse) RespError(err error) { diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 89dbb807..eb5e6000 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -18,12 +18,55 @@ import ( "github.com/getkin/kin-openapi/openapi3" "github.com/go-chi/chi/v5" + "github.com/oapi-codegen/runtime" + openapi_types "github.com/oapi-codegen/runtime/types" ) const ( - Jwt_tokenScopes = "jwt_token.Scopes" + Basic_authScopes = "basic_auth.Scopes" + Jwt_tokenScopes = "jwt_token.Scopes" ) +// Defines values for ObjectStatsPathType. +const ( + CommonPrefix ObjectStatsPathType = "common_prefix" + Object ObjectStatsPathType = "object" +) + +// ObjectStats defines model for ObjectStats. +type ObjectStats struct { + Checksum string `json:"checksum"` + + // ContentType Object media type + ContentType *string `json:"content_type,omitempty"` + Metadata *ObjectUserMetadata `json:"metadata,omitempty"` + + // Mtime Unix Epoch in seconds + Mtime int64 `json:"mtime"` + Path string `json:"path"` + PathType ObjectStatsPathType `json:"path_type"` + + // PhysicalAddress The location of the object on the underlying object store. + // Formatted as a native URI with the object store type as scheme ("s3://...", "gs://...", etc.) + // Or, in the case of presign=true, will be an HTTP URL to be consumed via regular HTTP GET + PhysicalAddress string `json:"physical_address"` + + // PhysicalAddressExpiry If present and nonzero, physical_address is a pre-signed URL and + // will expire at this Unix Epoch time. This will be shorter than + // the pre-signed URL lifetime if an authentication token is about + // to expire. + // + // This field is *optional*. + PhysicalAddressExpiry *int64 `json:"physical_address_expiry,omitempty"` + SizeBytes *int64 `json:"size_bytes,omitempty"` +} + +// ObjectStatsPathType defines model for ObjectStats.PathType. +type ObjectStatsPathType string + +// ObjectUserMetadata defines model for ObjectUserMetadata. +type ObjectUserMetadata map[string]string + // VersionResult defines model for VersionResult. type VersionResult struct { // ApiVersion runtime version @@ -33,6 +76,54 @@ type VersionResult struct { Version string `json:"version"` } +// DeleteObjectParams defines parameters for DeleteObject. +type DeleteObjectParams struct { + // Path relative to the ref + Path string `form:"path" json:"path"` +} + +// GetObjectParams defines parameters for GetObject. +type GetObjectParams struct { + Presign *bool `form:"presign,omitempty" json:"presign,omitempty"` + + // Path relative to the ref + Path string `form:"path" json:"path"` + + // Range Byte range to retrieve + Range *string `json:"Range,omitempty"` +} + +// HeadObjectParams defines parameters for HeadObject. +type HeadObjectParams struct { + // Path relative to the ref + Path string `form:"path" json:"path"` + + // Range Byte range to retrieve + Range *string `json:"Range,omitempty"` +} + +// UploadObjectMultipartBody defines parameters for UploadObject. +type UploadObjectMultipartBody struct { + // Content Only a single file per upload which must be named "content". + Content *openapi_types.File `json:"content,omitempty"` +} + +// UploadObjectParams defines parameters for UploadObject. +type UploadObjectParams struct { + // StorageClass Deprecated, this capability will not be supported in future releases. + StorageClass *string `form:"storageClass,omitempty" json:"storageClass,omitempty"` + + // Path relative to the ref + Path string `form:"path" json:"path"` + + // IfNoneMatch Currently supports only "*" to allow uploading an object only if one doesn't exist yet. + // Deprecated, this capability will not be supported in future releases. + IfNoneMatch *string `json:"If-None-Match,omitempty"` +} + +// UploadObjectMultipartRequestBody defines body for UploadObject for multipart/form-data ContentType. +type UploadObjectMultipartRequestBody UploadObjectMultipartBody + // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error @@ -106,10 +197,70 @@ func WithRequestEditorFn(fn RequestEditorFn) ClientOption { // The interface specification for the client above. type ClientInterface interface { + // DeleteObject request + DeleteObject(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetObject request + GetObject(ctx context.Context, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // HeadObject request + HeadObject(ctx context.Context, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UploadObjectWithBody request with any body + UploadObjectWithBody(ctx context.Context, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetVersion request GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) } +func (c *Client) DeleteObject(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteObjectRequest(c.Server, repository, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetObject(ctx context.Context, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetObjectRequest(c.Server, repository, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) HeadObject(ctx context.Context, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewHeadObjectRequest(c.Server, repository, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UploadObjectWithBody(ctx context.Context, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUploadObjectRequestWithBody(c.Server, repository, params, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewGetVersionRequest(c.Server) if err != nil { @@ -122,6 +273,293 @@ func (c *Client) GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) return c.Client.Do(req) } +// NewDeleteObjectRequest generates requests for DeleteObject +func NewDeleteObjectRequest(server string, repository string, params *DeleteObjectParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/repositories/%s/objects", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetObjectRequest generates requests for GetObject +func NewGetObjectRequest(server string, repository string, params *GetObjectParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/repositories/%s/objects", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Presign != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "presign", runtime.ParamLocationQuery, *params.Presign); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + if params != nil { + + if params.Range != nil { + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithLocation("simple", false, "Range", runtime.ParamLocationHeader, *params.Range) + if err != nil { + return nil, err + } + + req.Header.Set("Range", headerParam0) + } + + } + + return req, nil +} + +// NewHeadObjectRequest generates requests for HeadObject +func NewHeadObjectRequest(server string, repository string, params *HeadObjectParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/repositories/%s/objects", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("HEAD", queryURL.String(), nil) + if err != nil { + return nil, err + } + + if params != nil { + + if params.Range != nil { + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithLocation("simple", false, "Range", runtime.ParamLocationHeader, *params.Range) + if err != nil { + return nil, err + } + + req.Header.Set("Range", headerParam0) + } + + } + + return req, nil +} + +// NewUploadObjectRequestWithBody generates requests for UploadObject with any type of body +func NewUploadObjectRequestWithBody(server string, repository string, params *UploadObjectParams, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/repositories/%s/objects", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.StorageClass != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "storageClass", runtime.ParamLocationQuery, *params.StorageClass); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + if params != nil { + + if params.IfNoneMatch != nil { + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithLocation("simple", false, "If-None-Match", runtime.ParamLocationHeader, *params.IfNoneMatch) + if err != nil { + return nil, err + } + + req.Header.Set("If-None-Match", headerParam0) + } + + } + + return req, nil +} + // NewGetVersionRequest generates requests for GetVersion func NewGetVersionRequest(server string) (*http.Request, error) { var err error @@ -160,71 +598,278 @@ func (c *Client) applyEditors(ctx context.Context, req *http.Request, additional return err } } - return nil -} + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // DeleteObjectWithResponse request + DeleteObjectWithResponse(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) + + // GetObjectWithResponse request + GetObjectWithResponse(ctx context.Context, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*GetObjectResponse, error) + + // HeadObjectWithResponse request + HeadObjectWithResponse(ctx context.Context, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*HeadObjectResponse, error) + + // UploadObjectWithBodyWithResponse request with any body + UploadObjectWithBodyWithResponse(ctx context.Context, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) + + // GetVersionWithResponse request + GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) +} + +type DeleteObjectResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r DeleteObjectResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DeleteObjectResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetObjectResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r GetObjectResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetObjectResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type HeadObjectResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r HeadObjectResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r HeadObjectResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type UploadObjectResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *ObjectStats +} + +// Status returns HTTPResponse.Status +func (r UploadObjectResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UploadObjectResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetVersionResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *VersionResult +} + +// Status returns HTTPResponse.Status +func (r GetVersionResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetVersionResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// DeleteObjectWithResponse request returning *DeleteObjectResponse +func (c *ClientWithResponses) DeleteObjectWithResponse(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) { + rsp, err := c.DeleteObject(ctx, repository, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteObjectResponse(rsp) +} + +// GetObjectWithResponse request returning *GetObjectResponse +func (c *ClientWithResponses) GetObjectWithResponse(ctx context.Context, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*GetObjectResponse, error) { + rsp, err := c.GetObject(ctx, repository, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetObjectResponse(rsp) +} + +// HeadObjectWithResponse request returning *HeadObjectResponse +func (c *ClientWithResponses) HeadObjectWithResponse(ctx context.Context, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*HeadObjectResponse, error) { + rsp, err := c.HeadObject(ctx, repository, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseHeadObjectResponse(rsp) +} + +// UploadObjectWithBodyWithResponse request with arbitrary body returning *UploadObjectResponse +func (c *ClientWithResponses) UploadObjectWithBodyWithResponse(ctx context.Context, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) { + rsp, err := c.UploadObjectWithBody(ctx, repository, params, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUploadObjectResponse(rsp) +} + +// GetVersionWithResponse request returning *GetVersionResponse +func (c *ClientWithResponses) GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) { + rsp, err := c.GetVersion(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetVersionResponse(rsp) +} + +// ParseDeleteObjectResponse parses an HTTP response from a DeleteObjectWithResponse call +func ParseDeleteObjectResponse(rsp *http.Response) (*DeleteObjectResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DeleteObjectResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } -// ClientWithResponses builds on ClientInterface to offer response payloads -type ClientWithResponses struct { - ClientInterface + return response, nil } -// NewClientWithResponses creates a new ClientWithResponses, which wraps -// Client with return type handling -func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { - client, err := NewClient(server, opts...) +// ParseGetObjectResponse parses an HTTP response from a GetObjectWithResponse call +func ParseGetObjectResponse(rsp *http.Response) (*GetObjectResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - return &ClientWithResponses{client}, nil -} -// WithBaseURL overrides the baseURL. -func WithBaseURL(baseURL string) ClientOption { - return func(c *Client) error { - newBaseURL, err := url.Parse(baseURL) - if err != nil { - return err - } - c.Server = newBaseURL.String() - return nil + response := &GetObjectResponse{ + Body: bodyBytes, + HTTPResponse: rsp, } -} - -// ClientWithResponsesInterface is the interface specification for the client with responses above. -type ClientWithResponsesInterface interface { - // GetVersionWithResponse request - GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) -} -type GetVersionResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *VersionResult + return response, nil } -// Status returns HTTPResponse.Status -func (r GetVersionResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status +// ParseHeadObjectResponse parses an HTTP response from a HeadObjectWithResponse call +func ParseHeadObjectResponse(rsp *http.Response) (*HeadObjectResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err } - return http.StatusText(0) -} -// StatusCode returns HTTPResponse.StatusCode -func (r GetVersionResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + response := &HeadObjectResponse{ + Body: bodyBytes, + HTTPResponse: rsp, } - return 0 + + return response, nil } -// GetVersionWithResponse request returning *GetVersionResponse -func (c *ClientWithResponses) GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) { - rsp, err := c.GetVersion(ctx, reqEditors...) +// ParseUploadObjectResponse parses an HTTP response from a UploadObjectWithResponse call +func ParseUploadObjectResponse(rsp *http.Response) (*UploadObjectResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - return ParseGetVersionResponse(rsp) + + response := &UploadObjectResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest ObjectStats + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil } // ParseGetVersionResponse parses an HTTP response from a GetVersionWithResponse call @@ -255,6 +900,18 @@ func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) { // ServerInterface represents all server handlers. type ServerInterface interface { + // delete object. Missing objects will not return a NotFound error. + // (DELETE /repositories/{repository}/objects) + DeleteObject(w *JiaozifsResponse, r *http.Request, repository string, params DeleteObjectParams) + // get object content + // (GET /repositories/{repository}/objects) + GetObject(w *JiaozifsResponse, r *http.Request, repository string, params GetObjectParams) + // check if object exists + // (HEAD /repositories/{repository}/objects) + HeadObject(w *JiaozifsResponse, r *http.Request, repository string, params HeadObjectParams) + + // (POST /repositories/{repository}/objects) + UploadObject(w *JiaozifsResponse, r *http.Request, repository string, params UploadObjectParams) // return program and runtime version // (GET /version) GetVersion(w *JiaozifsResponse, r *http.Request) @@ -264,6 +921,29 @@ type ServerInterface interface { type Unimplemented struct{} +// delete object. Missing objects will not return a NotFound error. +// (DELETE /repositories/{repository}/objects) +func (_ Unimplemented) DeleteObject(w *JiaozifsResponse, r *http.Request, repository string, params DeleteObjectParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get object content +// (GET /repositories/{repository}/objects) +func (_ Unimplemented) GetObject(w *JiaozifsResponse, r *http.Request, repository string, params GetObjectParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// check if object exists +// (HEAD /repositories/{repository}/objects) +func (_ Unimplemented) HeadObject(w *JiaozifsResponse, r *http.Request, repository string, params HeadObjectParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// (POST /repositories/{repository}/objects) +func (_ Unimplemented) UploadObject(w *JiaozifsResponse, r *http.Request, repository string, params UploadObjectParams) { + w.WriteHeader(http.StatusNotImplemented) +} + // return program and runtime version // (GET /version) func (_ Unimplemented) GetVersion(w *JiaozifsResponse, r *http.Request) { @@ -279,6 +959,277 @@ type ServerInterfaceWrapper struct { type MiddlewareFunc func(http.Handler) http.Handler +// DeleteObject operation middleware +func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{"aaaa"}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{"aaaaa"}) + + // Parameter object where we will unmarshal all parameters from the context + var params DeleteObjectParams + + // ------------- Required query parameter "path" ------------- + + if paramValue := r.URL.Query().Get("path"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteObject(&JiaozifsResponse{w}, r, repository, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// GetObject operation middleware +func (siw *ServerInterfaceWrapper) GetObject(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{"aaaa"}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{"aaaaa"}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetObjectParams + + // ------------- Optional query parameter "presign" ------------- + + err = runtime.BindQueryParameter("form", true, false, "presign", r.URL.Query(), ¶ms.Presign) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "presign", Err: err}) + return + } + + // ------------- Required query parameter "path" ------------- + + if paramValue := r.URL.Query().Get("path"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + headers := r.Header + + // ------------- Optional header parameter "Range" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("Range")]; found { + var Range string + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Range", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "Range", valueList[0], &Range, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Range", Err: err}) + return + } + + params.Range = &Range + + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetObject(&JiaozifsResponse{w}, r, repository, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// HeadObject operation middleware +func (siw *ServerInterfaceWrapper) HeadObject(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{"aaaa"}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{"aaaaa"}) + + // Parameter object where we will unmarshal all parameters from the context + var params HeadObjectParams + + // ------------- Required query parameter "path" ------------- + + if paramValue := r.URL.Query().Get("path"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + headers := r.Header + + // ------------- Optional header parameter "Range" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("Range")]; found { + var Range string + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Range", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "Range", valueList[0], &Range, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Range", Err: err}) + return + } + + params.Range = &Range + + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.HeadObject(&JiaozifsResponse{w}, r, repository, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// UploadObject operation middleware +func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{"aaaa"}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{"aaaaa"}) + + // Parameter object where we will unmarshal all parameters from the context + var params UploadObjectParams + + // ------------- Optional query parameter "storageClass" ------------- + + err = runtime.BindQueryParameter("form", true, false, "storageClass", r.URL.Query(), ¶ms.StorageClass) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "storageClass", Err: err}) + return + } + + // ------------- Required query parameter "path" ------------- + + if paramValue := r.URL.Query().Get("path"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + headers := r.Header + + // ------------- Optional header parameter "If-None-Match" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("If-None-Match")]; found { + var IfNoneMatch string + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "If-None-Match", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "If-None-Match", valueList[0], &IfNoneMatch, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "If-None-Match", Err: err}) + return + } + + params.IfNoneMatch = &IfNoneMatch + + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UploadObject(&JiaozifsResponse{w}, r, repository, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // GetVersion operation middleware func (siw *ServerInterfaceWrapper) GetVersion(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -409,6 +1360,18 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl ErrorHandlerFunc: options.ErrorHandlerFunc, } + r.Group(func(r chi.Router) { + r.Delete(options.BaseURL+"/repositories/{repository}/objects", wrapper.DeleteObject) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/repositories/{repository}/objects", wrapper.GetObject) + }) + r.Group(func(r chi.Router) { + r.Head(options.BaseURL+"/repositories/{repository}/objects", wrapper.HeadObject) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/repositories/{repository}/objects", wrapper.UploadObject) + }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/version", wrapper.GetVersion) }) @@ -419,15 +1382,37 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "Gus4chqDEeO7Q764Wfw7AAD//58N5ajGGQAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index a9acb731..1ab95d18 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -49,6 +49,91 @@ components: api_version: type: string description: runtime version + ObjectUserMetadata: + type: object + additionalProperties: + type: string + + ObjectStats: + type: object + required: + - checksum + - physical_address + - path + - path_type + - mtime + properties: + path: + type: string + path_type: + type: string + enum: [common_prefix, object] + physical_address: + type: string + description: | + The location of the object on the underlying object store. + Formatted as a native URI with the object store type as scheme ("s3://...", "gs://...", etc.) + Or, in the case of presign=true, will be an HTTP URL to be consumed via regular HTTP GET + physical_address_expiry: + type: integer + format: int64 + description: | + If present and nonzero, physical_address is a pre-signed URL and + will expire at this Unix Epoch time. This will be shorter than + the pre-signed URL lifetime if an authentication token is about + to expire. + + This field is *optional*. + checksum: + type: string + size_bytes: + type: integer + format: int64 + mtime: + type: integer + format: int64 + description: Unix Epoch in seconds + metadata: + $ref: "#/components/schemas/ObjectUserMetadata" + content_type: + type: string + description: Object media type + + ObjectStatsList: + type: object + required: + - pagination + - results + properties: + pagination: + $ref: "#/components/schemas/Pagination" + results: + type: array + items: + $ref: "#/components/schemas/ObjectStats" + + Pagination: + type: object + required: + - has_more + - max_per_page + - results + - next_offset + properties: + has_more: + type: boolean + description: Next page is available + next_offset: + type: string + description: Token used to retrieve the next page + results: + type: integer + minimum: 0 + description: Number of values found in the results + max_per_page: + type: integer + minimum: 0 + description: Maximal number of entries per page Error: type: object required: @@ -74,3 +159,223 @@ paths: schema: $ref: "#/components/schemas/VersionResult" + /repositories/{repository}/objects: + parameters: + - in: path + name: repository + required: true + schema: + type: string + - in: query + name: path + description: relative to the ref + required: true + schema: + type: string + get: + tags: + - objects + operationId: getObject + summary: get object content + parameters: + - in: header + name: Range + description: Byte range to retrieve + example: "bytes=0-1023" + required: false + schema: + type: string + pattern: '^bytes=((\d*-\d*,? ?)+)$' + - in: query + name: presign + required: false + schema: + type: boolean + responses: + 200: + description: object content + content: + application/octet-stream: + schema: + type: string + format: binary + headers: + Content-Length: + schema: + type: integer + format: int64 + Last-Modified: + schema: + type: string + ETag: + schema: + type: string + 206: + description: partial object content + content: + application/octet-stream: + schema: + type: string + format: binary + headers: + Content-Length: + schema: + type: integer + format: int64 + Content-Range: + schema: + type: string + pattern: '^bytes=((\d*-\d*,? ?)+)$' + Last-Modified: + schema: + type: string + ETag: + schema: + type: string + 302: + description: Redirect to a pre-signed URL for the object + headers: + Location: + schema: + type: string + 401: + description: Unauthorized + 404: + description: object not found + 410: + description: object expired + 416: + description: Requested Range Not Satisfiable + 420: + description: too many requests + head: + tags: + - objects + operationId: headObject + summary: check if object exists + parameters: + - in: header + name: Range + description: Byte range to retrieve + example: "bytes=0-1023" + required: false + schema: + type: string + pattern: '^bytes=((\d*-\d*,? ?)+)$' + responses: + 200: + description: object exists + headers: + Content-Length: + schema: + type: integer + format: int64 + Last-Modified: + schema: + type: string + ETag: + schema: + type: string + 206: + description: partial object content info + headers: + Content-Length: + schema: + type: integer + format: int64 + Content-Range: + schema: + type: string + pattern: '^bytes=((\d*-\d*,? ?)+)$' + Last-Modified: + schema: + type: string + ETag: + schema: + type: string + 401: + description: Unauthorized + 404: + description: object not found + 410: + description: object expired + 416: + description: Requested Range Not Satisfiable + 420: + description: too many requests + default: + description: internal server error + post: + tags: + - objects + operationId: uploadObject + x-validation-exclude-body: true + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + content: + description: Only a single file per upload which must be named "content". + type: string + format: binary + application/octet-stream: + schema: + type: string + format: binary + + parameters: + - in: query + name: storageClass + description: Deprecated, this capability will not be supported in future releases. + required: false + deprecated: true + schema: + type: string + - in: header + name: If-None-Match + description: | + Currently supports only "*" to allow uploading an object only if one doesn't exist yet. + Deprecated, this capability will not be supported in future releases. + example: "*" + required: false + deprecated: true + schema: + type: string + pattern: '^\*$' # Currently, only "*" is supported + responses: + 201: + description: object metadata + content: + application/json: + schema: + $ref: "#/components/schemas/ObjectStats" + 400: + description: ValidationError + 401: + description: Unauthorized ValidationError + 403: + description: Forbidden + 404: + description: url not found + 412: + description: PreconditionFailed + 420: + description: too many requests + delete: + tags: + - objects + operationId: deleteObject + summary: delete object. Missing objects will not return a NotFound error. + responses: + 204: + description: object deleted successfully + 401: + description: Unauthorized + 403: + description: Forbidden + 404: + description: NotFound + 420: + description: too many requests diff --git a/cmd/daemon.go b/cmd/daemon.go index fbb6d5cb..cd5e8038 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -53,6 +53,10 @@ var daemonCmd = &cobra.Command{ fx_opt.Override(new(*bun.DB), models.SetupDatabase), fx_opt.Override(fx_opt.NextInvoke(), migrations.MigrateDatabase), fx_opt.Override(new(*models.IUserRepo), models.NewUserRepo), + fx_opt.Override(new(*models.IRepositoryRepo), models.NewRepositoryRepo), + fx_opt.Override(new(*models.IRefRepo), models.NewRefRepo), + fx_opt.Override(new(*models.IObjectRepo), models.NewObjectRepo), + //api fx_opt.Override(fx_opt.NextInvoke(), apiImpl.SetupAPI), ) diff --git a/controller/object_ctl.go b/controller/object_ctl.go new file mode 100644 index 00000000..84cbd926 --- /dev/null +++ b/controller/object_ctl.go @@ -0,0 +1,32 @@ +package controller + +import ( + "net/http" + + "github.com/jiaozifs/jiaozifs/api" + "go.uber.org/fx" +) + +type ObjectController struct { + fx.In +} + +func (A ObjectController) DeleteObject(_ *api.JiaozifsResponse, r *http.Request, repository string, params api.DeleteObjectParams) { //nolint + //TODO implement me + panic("implement me") +} + +func (A ObjectController) GetObject(_ *api.JiaozifsResponse, r *http.Request, repository string, params api.GetObjectParams) { //nolint + //TODO implement me + panic("implement me") +} + +func (A ObjectController) HeadObject(_ *api.JiaozifsResponse, r *http.Request, repository string, params api.HeadObjectParams) { //nolint + //TODO implement me + panic("implement me") +} + +func (A ObjectController) UploadObject(_ *api.JiaozifsResponse, r *http.Request, repository string, params api.UploadObjectParams) { //nolint + //TODO implement me + panic("implement me") +} diff --git a/api/api_impl/common.go b/controller/version_ctl.go similarity index 65% rename from api/api_impl/common.go rename to controller/version_ctl.go index dd71cde3..c1cfbb30 100644 --- a/api/api_impl/common.go +++ b/controller/version_ctl.go @@ -1,4 +1,4 @@ -package apiimpl +package controller import ( "net/http" @@ -8,13 +8,11 @@ import ( "go.uber.org/fx" ) -var _ api.ServerInterface = (*APIController)(nil) - -type APIController struct { +type VersionController struct { fx.In } -func (A APIController) GetVersion(w *api.JiaozifsResponse, _ *http.Request) { +func (A VersionController) GetVersion(w *api.JiaozifsResponse, _ *http.Request) { swagger, err := api.GetSwagger() if err != nil { w.RespError(err) diff --git a/go.mod b/go.mod index 519d3f4b..9147bc05 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/s3 v1.47.1 github.com/aws/smithy-go v1.18.1 github.com/benburkert/dns v0.0.0-20190225204957-d356cf78cdfc + github.com/brianvoe/gofakeit/v6 v6.25.0 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/deepmap/oapi-codegen/v2 v2.0.1-0.20231120160225-add3126ee845 github.com/fergusstrange/embedded-postgres v1.25.0 @@ -30,8 +31,10 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/mapstructure v1.5.0 github.com/oapi-codegen/nethttp-middleware v1.0.1 + github.com/oapi-codegen/runtime v1.1.0 github.com/ory/dockertest/v3 v3.10.0 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 + github.com/pjbgf/sha1cd v0.3.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.17.0 github.com/puzpuzpuz/xsync v1.5.2 @@ -47,6 +50,7 @@ require ( golang.org/x/exp v0.0.0-20230905200255-921286631fa9 golang.org/x/oauth2 v0.13.0 google.golang.org/api v0.147.0 + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c gopkg.in/yaml.v2 v2.4.0 ) @@ -60,6 +64,7 @@ require ( github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.3 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.8 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.7 // indirect @@ -106,6 +111,8 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.0 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/magiconair/properties v1.8.7 // indirect @@ -129,6 +136,7 @@ require ( github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.11.1 // indirect + github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/rs/xid v1.5.0 // indirect github.com/sagikazarmark/locafero v0.3.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect diff --git a/go.sum b/go.sum index 27da620d..6e6f6997 100644 --- a/go.sum +++ b/go.sum @@ -65,6 +65,9 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/aws/aws-sdk-go-v2 v1.23.4 h1:2P20ZjH0ouSAu/6yZep8oCmTReathLuEu6dwoqEgjts= github.com/aws/aws-sdk-go-v2 v1.23.4/go.mod h1:t3szzKfP0NeRU27uBFczDivYJjsmSnqI8kIvKyWb9ds= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.3 h1:Zx9+31KyB8wQna6SXFWOewlgoY5uGdDAu6PTOEU3OQI= @@ -109,6 +112,9 @@ github.com/benburkert/dns v0.0.0-20190225204957-d356cf78cdfc h1:eyDlmf21vuKN61Wo github.com/benburkert/dns v0.0.0-20190225204957-d356cf78cdfc/go.mod h1:6ul4nJKqsreAIBK5lUkibcUn2YBU6CvDzlKDH+dtZsQ= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= +github.com/brianvoe/gofakeit/v6 v6.25.0 h1:ZpFjktOpLZUeF8q223o0rUuXtA+m5qW5srjvVi+JkXk= +github.com/brianvoe/gofakeit/v6 v6.25.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -288,6 +294,7 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= @@ -299,6 +306,7 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -345,6 +353,8 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/oapi-codegen/nethttp-middleware v1.0.1 h1:ZWvwfnMU0eloHX1VEJmQscQm3741t0vCm0eSIie1NIo= github.com/oapi-codegen/nethttp-middleware v1.0.1/go.mod h1:P7xtAvpoqNB+5obR9qRCeefH7YlXWSK3KgPs/9WB8tE= +github.com/oapi-codegen/runtime v1.1.0 h1:rJpoNUawn5XTvekgfkvSZr0RqEnoYpFkyvrzfWeFKWM= +github.com/oapi-codegen/runtime v1.1.0/go.mod h1:BeSfBkWWWnAnGdyS+S/GnlbmHKzf8/hwkvelJZDeKA8= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= @@ -359,8 +369,11 @@ github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55F github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -380,7 +393,9 @@ github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/ github.com/puzpuzpuz/xsync v1.5.2 h1:yRAP4wqSOZG+/4pxJ08fPTwrfL0IzE/LKQ/cw509qGY= github.com/puzpuzpuz/xsync v1.5.2/go.mod h1:K98BYhX3k1dQ2M63t1YNVDanbwUPmBCAhNmVrrxfiGg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -402,6 +417,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/models/filemode/filemode.go b/models/filemode/filemode.go new file mode 100644 index 00000000..9840970d --- /dev/null +++ b/models/filemode/filemode.go @@ -0,0 +1,190 @@ +package filemode + +import ( + "encoding/binary" + "fmt" + "os" + "strconv" +) + +// A FileMode represents the kind of tree entries used by git. It +// resembles regular file systems modes, although FileModes are +// considerably simpler (there are not so many), and there are some, +// like Submodule that has no file system equivalent. +// +// The code is copied from go git just to ensure compatibility and only partially implemented. +type FileMode uint32 + +const ( + // Empty is used as the FileMode of tree elements when comparing + // trees in the following situations: + // + // - the mode of tree elements before their creation. - the mode of + // tree elements after their deletion. - the mode of unmerged + // elements when checking the index. + // + // Empty has no file system equivalent. As Empty is the zero value + // of FileMode, it is also returned by New and + // NewFromOsNewFromOSFileMode along with an error, when they fail. + Empty FileMode = 0 + // Dir represent a Directory. + Dir FileMode = 0040000 + // Regular represent non-executable files. Please note this is not + // the same as golang regular files, which include executable files. + Regular FileMode = 0100644 + // Deprecated represent non-executable files with the group writable + // bit set. This mode was supported by the first versions of git, + // but it has been deprecated nowadays. This library uses them + // internally, so you can read old packfiles, but will treat them as + // Regulars when interfacing with the outside world. This is the + // standard git behaviour. + Deprecated FileMode = 0100664 + // Executable represents executable files. + Executable FileMode = 0100755 + // Symlink represents symbolic links to files. + Symlink FileMode = 0120000 + // Submodule represents git submodules. This mode has no file system + // equivalent. + Submodule FileMode = 0160000 +) + +// New takes the octal string representation of a FileMode and returns +// the FileMode and a nil error. If the string can not be parsed to a +// 32 bit unsigned octal number, it returns Empty and the parsing error. +// +// Example: "40000" means Dir, "100644" means Regular. +// +// Please note this function does not check if the returned FileMode +// is valid in git or if it is malformed. For instance, "1" will +// return the malformed FileMode(1) and a nil error. +func New(s string) (FileMode, error) { + n, err := strconv.ParseUint(s, 8, 32) + if err != nil { + return Empty, err + } + + return FileMode(n), nil +} + +// NewFromOSFileMode returns the FileMode used by git to represent +// the provided file system modes and a nil error on success. If the +// file system mode cannot be mapped to any valid git mode (as with +// sockets or named pipes), it will return Empty and an error. +// +// Note that some git modes cannot be generated from os.FileModes, like +// Deprecated and Submodule; while Empty will be returned, along with an +// error, only when the method fails. +func NewFromOSFileMode(m os.FileMode) (FileMode, error) { + if m.IsRegular() { + if isSetTemporary(m) { + return Empty, fmt.Errorf("no equivalent git mode for %s", m) + } + if isSetCharDevice(m) { + return Empty, fmt.Errorf("no equivalent git mode for %s", m) + } + if isSetUserExecutable(m) { + return Executable, nil + } + return Regular, nil + } + + if m.IsDir() { + return Dir, nil + } + + if isSetSymLink(m) { + return Symlink, nil + } + + return Empty, fmt.Errorf("no equivalent git mode for %s", m) +} + +func isSetCharDevice(m os.FileMode) bool { + return m&os.ModeCharDevice != 0 +} + +func isSetTemporary(m os.FileMode) bool { + return m&os.ModeTemporary != 0 +} + +func isSetUserExecutable(m os.FileMode) bool { + return m&0100 != 0 +} + +func isSetSymLink(m os.FileMode) bool { + return m&os.ModeSymlink != 0 +} + +// Bytes return a slice of 4 bytes with the mode in little endian +// encoding. +func (m FileMode) Bytes() []byte { + ret := make([]byte, 4) + binary.LittleEndian.PutUint32(ret, uint32(m)) + return ret +} + +// IsMalformed returns if the FileMode should not appear in a git packfile, +// this is: Empty and any other mode not mentioned as a constant in this +// package. +func (m FileMode) IsMalformed() bool { + return m != Dir && + m != Regular && + m != Deprecated && + m != Executable && + m != Symlink && + m != Submodule +} + +// String returns the FileMode as a string in the standard git format, +// this is, an octal number padded with ceros to 7 digits. Malformed +// modes are printed in that same format, for easier debugging. +// +// Example: Regular is "0100644", Empty is "0000000". +func (m FileMode) String() string { + return fmt.Sprintf("%07o", uint32(m)) +} + +// IsRegular returns if the FileMode represents that of a regular file, +// this is, either Regular or Deprecated. Please note that Executable +// are not regular even though in the UNIX tradition, they usually are: +// See the IsFile method. +func (m FileMode) IsRegular() bool { + return m == Regular || + m == Deprecated +} + +// IsFile returns if the FileMode represents that of a file, this is, +// Regular, Deprecated, Executable or Link. +func (m FileMode) IsFile() bool { + return m == Regular || + m == Deprecated || + m == Executable || + m == Symlink +} + +// ToOSFileMode returns the os.FileMode to be used when creating file +// system elements with the given git mode and a nil error on success. +// +// When the provided mode cannot be mapped to a valid file system mode +// (e.g. Submodule) it returns os.FileMode(0) and an error. +// +// The returned file mode does not take into account the umask. +func (m FileMode) ToOSFileMode() (os.FileMode, error) { + switch m { + case Dir: + return os.ModePerm | os.ModeDir, nil + case Submodule: + return os.ModePerm | os.ModeDir, nil + case Regular: + return os.FileMode(0644), nil + // Deprecated is no longer allowed: treated as a Regular instead + case Deprecated: + return os.FileMode(0644), nil + case Executable: + return os.FileMode(0755), nil + case Symlink: + return os.ModePerm | os.ModeSymlink, nil + } + + return os.FileMode(0), fmt.Errorf("malformed mode (%s)", m) +} diff --git a/models/filemode/filemode_test.go b/models/filemode/filemode_test.go new file mode 100644 index 00000000..02bf5c5c --- /dev/null +++ b/models/filemode/filemode_test.go @@ -0,0 +1,348 @@ +package filemode + +import ( + "os" + "testing" + + . "gopkg.in/check.v1" //nolint +) + +func Test(t *testing.T) { TestingT(t) } + +type ModeSuite struct{} + +var _ = Suite(&ModeSuite{}) + +func (s *ModeSuite) TestNew(c *C) { + for _, test := range [...]struct { + input string + expected FileMode + }{ + // these are the ones used in the packfile codification + // of the tree entries + {input: "40000", expected: Dir}, + {input: "100644", expected: Regular}, + {input: "100664", expected: Deprecated}, + {input: "100755", expected: Executable}, + {input: "120000", expected: Symlink}, + {input: "160000", expected: Submodule}, + // these are are not used by standard git to codify modes in + // packfiles, but they often appear when parsing some git + // outputs ("git diff-tree", for instance). + {input: "000000", expected: Empty}, + {input: "040000", expected: Dir}, + // these are valid inputs, but probably means there is a bug + // somewhere. + {input: "0", expected: Empty}, + {input: "42", expected: FileMode(042)}, + {input: "00000000000100644", expected: Regular}, + } { + comment := Commentf("input = %q", test.input) + obtained, err := New(test.input) + c.Assert(obtained, Equals, test.expected, comment) + c.Assert(err, IsNil, comment) + } +} + +func (s *ModeSuite) TestNewErrors(c *C) { + for _, input := range [...]string{ + "0x81a4", // Regular in hex + "-rw-r--r--", // Regular in default UNIX representation + "", + "-42", + "9", // this is no octal + "09", // looks like octal, but it is not + "mode", + "-100644", + "+100644", + } { + comment := Commentf("input = %q", input) + obtained, err := New(input) + c.Assert(obtained, Equals, Empty, comment) + c.Assert(err, Not(IsNil), comment) + } +} + +// fixtures for testing NewModeFromOSFileMode +type fixture struct { + input os.FileMode + expected FileMode + err string // error regexp, empty string for nil error +} + +func (f fixture) test(c *C) { + obtained, err := NewFromOSFileMode(f.input) + comment := Commentf("input = %s (%07o)", f.input, uint32(f.input)) + c.Assert(obtained, Equals, f.expected, comment) + if f.err != "" { + c.Assert(err, ErrorMatches, f.err, comment) + } else { + c.Assert(err, IsNil, comment) + } +} + +func (s *ModeSuite) TestNewFromOsFileModeSimplePerms(c *C) { + for _, f := range [...]fixture{ + {os.FileMode(0755) | os.ModeDir, Dir, ""}, // drwxr-xr-x + {os.FileMode(0700) | os.ModeDir, Dir, ""}, // drwx------ + {os.FileMode(0500) | os.ModeDir, Dir, ""}, // dr-x------ + {os.FileMode(0644), Regular, ""}, // -rw-r--r-- + {os.FileMode(0660), Regular, ""}, // -rw-rw---- + {os.FileMode(0640), Regular, ""}, // -rw-r----- + {os.FileMode(0600), Regular, ""}, // -rw------- + {os.FileMode(0400), Regular, ""}, // -r-------- + {os.FileMode(0000), Regular, ""}, // ---------- + {os.FileMode(0755), Executable, ""}, // -rwxr-xr-x + {os.FileMode(0700), Executable, ""}, // -rwx------ + {os.FileMode(0500), Executable, ""}, // -r-x------ + {os.FileMode(0744), Executable, ""}, // -rwxr--r-- + {os.FileMode(0540), Executable, ""}, // -r-xr----- + {os.FileMode(0550), Executable, ""}, // -r-xr-x--- + {os.FileMode(0777) | os.ModeSymlink, Symlink, ""}, // Lrwxrwxrwx + } { + f.test(c) + } +} + +func (s *ModeSuite) TestNewFromOsFileModeAppend(c *C) { + // append files are just regular files + fixture{ + input: os.FileMode(0644) | os.ModeAppend, // arw-r--r-- + expected: Regular, err: "", + }.test(c) +} + +func (s *ModeSuite) TestNewFromOsFileModeExclusive(c *C) { + // exclusive files are just regular or executable files + fixture{ + input: os.FileMode(0644) | os.ModeExclusive, // lrw-r--r-- + expected: Regular, err: "", + }.test(c) + + fixture{ + input: os.FileMode(0755) | os.ModeExclusive, // lrwxr-xr-x + expected: Executable, err: "", + }.test(c) +} + +func (s *ModeSuite) TestNewFromOsFileModeTemporary(c *C) { + // temporary files are ignored + fixture{ + input: os.FileMode(0644) | os.ModeTemporary, // Trw-r--r-- + expected: Empty, err: "no equivalent.*", + }.test(c) + + fixture{ + input: os.FileMode(0755) | os.ModeTemporary, // Trwxr-xr-x + expected: Empty, err: "no equivalent.*", + }.test(c) +} + +func (s *ModeSuite) TestNewFromOsFileModeDevice(c *C) { + // device files has no git equivalent + fixture{ + input: os.FileMode(0644) | os.ModeDevice, // Drw-r--r-- + expected: Empty, err: "no equivalent.*", + }.test(c) +} + +func (s *ModeSuite) TestNewFromOsFileNamedPipe(c *C) { + // named pipes files has not git equivalent + fixture{ + input: os.FileMode(0644) | os.ModeNamedPipe, // prw-r--r-- + expected: Empty, err: "no equivalent.*", + }.test(c) +} + +func (s *ModeSuite) TestNewFromOsFileModeSocket(c *C) { + // sockets has no git equivalent + fixture{ + input: os.FileMode(0644) | os.ModeSocket, // Srw-r--r-- + expected: Empty, err: "no equivalent.*", + }.test(c) +} + +func (s *ModeSuite) TestNewFromOsFileModeSetuid(c *C) { + // Setuid are just executables + fixture{ + input: os.FileMode(0755) | os.ModeSetuid, // urwxr-xr-x + expected: Executable, err: "", + }.test(c) +} + +func (s *ModeSuite) TestNewFromOsFileModeSetgid(c *C) { + // Setguid are regular or executables, depending on the owner perms + fixture{ + input: os.FileMode(0644) | os.ModeSetgid, // grw-r--r-- + expected: Regular, err: "", + }.test(c) + + fixture{ + input: os.FileMode(0755) | os.ModeSetgid, // grwxr-xr-x + expected: Executable, err: "", + }.test(c) +} + +func (s *ModeSuite) TestNewFromOsFileModeCharDevice(c *C) { + // char devices has no git equivalent + fixture{ + input: os.FileMode(0644) | os.ModeCharDevice, // crw-r--r-- + expected: Empty, err: "no equivalent.*", + }.test(c) +} + +func (s *ModeSuite) TestNewFromOsFileModeSticky(c *C) { + // dirs with the sticky bit are just dirs + fixture{ + input: os.FileMode(0755) | os.ModeDir | os.ModeSticky, // dtrwxr-xr-x + expected: Dir, err: "", + }.test(c) +} + +func (s *ModeSuite) TestByte(c *C) { + for _, test := range [...]struct { + input FileMode + expected []byte + }{ + {FileMode(0), []byte{0x00, 0x00, 0x00, 0x00}}, + {FileMode(1), []byte{0x01, 0x00, 0x00, 0x00}}, + {FileMode(15), []byte{0x0f, 0x00, 0x00, 0x00}}, + {FileMode(16), []byte{0x10, 0x00, 0x00, 0x00}}, + {FileMode(255), []byte{0xff, 0x00, 0x00, 0x00}}, + {FileMode(256), []byte{0x00, 0x01, 0x00, 0x00}}, + {Empty, []byte{0x00, 0x00, 0x00, 0x00}}, + {Dir, []byte{0x00, 0x40, 0x00, 0x00}}, + {Regular, []byte{0xa4, 0x81, 0x00, 0x00}}, + {Deprecated, []byte{0xb4, 0x81, 0x00, 0x00}}, + {Executable, []byte{0xed, 0x81, 0x00, 0x00}}, + {Symlink, []byte{0x00, 0xa0, 0x00, 0x00}}, + {Submodule, []byte{0x00, 0xe0, 0x00, 0x00}}, + } { + c.Assert(test.input.Bytes(), DeepEquals, test.expected, + Commentf("input = %s", test.input)) + } +} + +func (s *ModeSuite) TestIsMalformed(c *C) { + for _, test := range [...]struct { + mode FileMode + expected bool + }{ + {Empty, true}, + {Dir, false}, + {Regular, false}, + {Deprecated, false}, + {Executable, false}, + {Symlink, false}, + {Submodule, false}, + {FileMode(01), true}, + {FileMode(010), true}, + {FileMode(0100), true}, + {FileMode(01000), true}, + {FileMode(010000), true}, + {FileMode(0100000), true}, + } { + c.Assert(test.mode.IsMalformed(), Equals, test.expected) + } +} + +func (s *ModeSuite) TestString(c *C) { + for _, test := range [...]struct { + mode FileMode + expected string + }{ + {Empty, "0000000"}, + {Dir, "0040000"}, + {Regular, "0100644"}, + {Deprecated, "0100664"}, + {Executable, "0100755"}, + {Symlink, "0120000"}, + {Submodule, "0160000"}, + {FileMode(01), "0000001"}, + {FileMode(010), "0000010"}, + {FileMode(0100), "0000100"}, + {FileMode(01000), "0001000"}, + {FileMode(010000), "0010000"}, + {FileMode(0100000), "0100000"}, + } { + c.Assert(test.mode.String(), Equals, test.expected) + } +} + +func (s *ModeSuite) TestIsRegular(c *C) { + for _, test := range [...]struct { + mode FileMode + expected bool + }{ + {Empty, false}, + {Dir, false}, + {Regular, true}, + {Deprecated, true}, + {Executable, false}, + {Symlink, false}, + {Submodule, false}, + {FileMode(01), false}, + {FileMode(010), false}, + {FileMode(0100), false}, + {FileMode(01000), false}, + {FileMode(010000), false}, + {FileMode(0100000), false}, + } { + c.Assert(test.mode.IsRegular(), Equals, test.expected) + } +} + +func (s *ModeSuite) TestIsFile(c *C) { + for _, test := range [...]struct { + mode FileMode + expected bool + }{ + {Empty, false}, + {Dir, false}, + {Regular, true}, + {Deprecated, true}, + {Executable, true}, + {Symlink, true}, + {Submodule, false}, + {FileMode(01), false}, + {FileMode(010), false}, + {FileMode(0100), false}, + {FileMode(01000), false}, + {FileMode(010000), false}, + {FileMode(0100000), false}, + } { + c.Assert(test.mode.IsFile(), Equals, test.expected) + } +} + +func (s *ModeSuite) TestToOSFileMode(c *C) { + for _, test := range [...]struct { + input FileMode + expected os.FileMode + errRegExp string // empty string for nil error + }{ + {Empty, os.FileMode(0), "malformed.*"}, + {Dir, os.ModePerm | os.ModeDir, ""}, + {Regular, os.FileMode(0644), ""}, + {Deprecated, os.FileMode(0644), ""}, + {Executable, os.FileMode(0755), ""}, + {Symlink, os.ModePerm | os.ModeSymlink, ""}, + {Submodule, os.ModePerm | os.ModeDir, ""}, + {FileMode(01), os.FileMode(0), "malformed.*"}, + {FileMode(010), os.FileMode(0), "malformed.*"}, + {FileMode(0100), os.FileMode(0), "malformed.*"}, + {FileMode(01000), os.FileMode(0), "malformed.*"}, + {FileMode(010000), os.FileMode(0), "malformed.*"}, + {FileMode(0100000), os.FileMode(0), "malformed.*"}, + } { + obtained, err := test.input.ToOSFileMode() + comment := Commentf("input = %s", test.input) + if test.errRegExp != "" { + c.Assert(obtained, Equals, os.FileMode(0), comment) + c.Assert(err, ErrorMatches, test.errRegExp, comment) + } else { + c.Assert(obtained, Equals, test.expected, comment) + c.Assert(err, IsNil, comment) + } + } +} diff --git a/models/migrations/20210505110026_init_project.go b/models/migrations/20210505110026_init_project.go index e317fe68..6980d971 100644 --- a/models/migrations/20210505110026_init_project.go +++ b/models/migrations/20210505110026_init_project.go @@ -9,11 +9,13 @@ import ( func init() { Migrations.MustRegister(func(ctx context.Context, db *bun.DB) error { + //common _, err := db.Exec(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp";`) if err != nil { return err } + //user _, err = db.NewCreateTable(). Model((*models.User)(nil)). Exec(ctx) @@ -26,6 +28,31 @@ func init() { Index("name_idx"). Column("name"). Exec(ctx) + if err != nil { + return err + } + //repository + _, err = db.NewCreateTable(). + Model((*models.Repository)(nil)). + Exec(ctx) + if err != nil { + return err + } + + //ref + _, err = db.NewCreateTable(). + Model((*models.Ref)(nil)). + Exec(ctx) + if err != nil { + return err + } + //object + _, err = db.NewCreateTable(). + Model((*models.Object)(nil)). + Exec(ctx) + if err != nil { + return err + } return err }, nil) } diff --git a/models/object.go b/models/object.go new file mode 100644 index 00000000..263bb913 --- /dev/null +++ b/models/object.go @@ -0,0 +1,114 @@ +package models + +import ( + "context" + "time" + + "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/models/filemode" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/uptrace/bun" +) + +// ObjectType internal object type +// Integer values from 0 to 7 map to those exposed by git. +// AnyObject is used to represent any from 0 to 7. +type ObjectType int8 + +const ( + InvalidObject ObjectType = 0 + CommitObject ObjectType = 1 + TreeObject ObjectType = 2 + BlobObject ObjectType = 3 + TagObject ObjectType = 4 +) + +// Signature is used to identify who and when created a commit or tag. +type Signature struct { + // Name represents a person name. It is an arbitrary string. + Name string `bun:"name"` + // Email is an email, but it cannot be assumed to be well-formed. + Email string `bun:"email"` + // When is the timestamp of the signature. + When time.Time `bun:"when"` +} + +type TreeEntry struct { + Name string `bun:"name"` + Mode filemode.FileMode `bun:"mode"` + Hash hash.Hash `bun:"hash"` +} + +type Object struct { + bun.BaseModel `bun:"table:object"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` + Hash hash.Hash `bun:"hash,type:bytea"` + Size int64 `bun:"size"` + //tree + SubObject []TreeEntry `bun:"subObj,type:jsonb"` + + //////********commit********//////// + // Author is the original author of the commit. + Author Signature `bun:"author,type:jsonb"` + // Committer is the one performing the commit, might be different from + // Author. + Committer Signature `bun:"committer,type:jsonb"` + // MergeTag is the embedded tag object when a merge commit is created by + // merging a signed tag. + MergeTag string `bun:"merge_tag"` //todo + // Message is the commit/tag message, contains arbitrary text. + Message string `bun:"message"` + // TreeHash is the hash of the root tree of the commit. + TreeHash hash.Hash `bun:"tree_hash,type:bytea"` + // ParentHashes are the hashes of the parent commits of the commit. + ParentHashes []hash.Hash `bun:"parent_hashes,type:bytea[]"` + + //////********commit********//////// + // Name of the tag. + Name string `bun:"name"` + // Tagger is the one who created the tag. + Tagger Signature `bun:"tagger,type:jsonb"` + // TargetType is the object type of the target. + TargetType ObjectType `bun:"target_type"` + // Target is the hash of the target object. + Target hash.Hash `bun:"target,type:bytea"` +} + +type IObjectRepo interface { + Insert(ctx context.Context, repo *Object) (*Object, error) + Get(ctx context.Context, id uuid.UUID) (*Object, error) + Count(ctx context.Context) (int, error) + List(ctx context.Context) ([]Object, error) +} + +var _ IObjectRepo = (*ObjectRepo)(nil) + +type ObjectRepo struct { + *bun.DB +} + +func NewObjectRepo(db *bun.DB) IObjectRepo { + return &ObjectRepo{db} +} + +func (o ObjectRepo) Insert(ctx context.Context, obj *Object) (*Object, error) { + _, err := o.DB.NewInsert().Model(obj).Exec(ctx) + if err != nil { + return nil, err + } + return obj, nil +} + +func (o ObjectRepo) Get(ctx context.Context, id uuid.UUID) (*Object, error) { + obj := &Object{} + return obj, o.DB.NewSelect().Model(obj).Where("id = ?", id).Scan(ctx) +} + +func (o ObjectRepo) Count(ctx context.Context) (int, error) { + return o.DB.NewSelect().Model((*Object)(nil)).Count(ctx) +} + +func (o ObjectRepo) List(ctx context.Context) ([]Object, error) { + obj := []Object{} + return obj, o.DB.NewSelect().Model(&obj).Scan(ctx) +} diff --git a/models/object_test.go b/models/object_test.go new file mode 100644 index 00000000..a7923575 --- /dev/null +++ b/models/object_test.go @@ -0,0 +1,39 @@ +package models_test + +import ( + "context" + "testing" + + "github.com/brianvoe/gofakeit/v6" + "github.com/google/go-cmp/cmp" + "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/models" + "github.com/stretchr/testify/require" +) + +func TestObjectRepo_Insert(t *testing.T) { + ctx := context.Background() + postgres, db := setup(ctx, t) + defer postgres.Stop() //nolint + + repo := models.NewObjectRepo(db) + + objModel := &models.Object{} + require.NoError(t, gofakeit.Struct(objModel)) + newObj, err := repo.Insert(ctx, objModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newObj.ID) + + count, err := repo.Count(ctx) + require.NoError(t, err) + require.Equal(t, 1, count) + + list, err := repo.List(ctx) + require.NoError(t, err) + require.Equal(t, 1, len(list)) + + ref, err := repo.Get(ctx, newObj.ID) + require.NoError(t, err) + + require.True(t, cmp.Equal(newObj, ref, dbTimeCmpOpt)) +} diff --git a/models/ref.go b/models/ref.go new file mode 100644 index 00000000..889435be --- /dev/null +++ b/models/ref.go @@ -0,0 +1,54 @@ +package models + +import ( + "context" + "time" + + "github.com/google/uuid" + "github.com/uptrace/bun" +) + +type Ref struct { + bun.BaseModel `bun:"table:ref"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` + // RepositoryId which repository this branch belong + RepositoryID uuid.UUID `bun:"repository_id,type:uuid,notnull"` + CommitHash uuid.UUID `bun:"commit_hash,type:uuid,notnull"` + // Path name/path of branch + Path string `bun:"path,notnull"` + // Description + Description string `bun:"description"` + // CreateId who create this branch + CreateID uuid.UUID `bun:"create_id,type:uuid,notnull"` + + CreatedAt time.Time `bun:"created_at"` + UpdatedAt time.Time `bun:"updated_at"` +} + +type IRefRepo interface { + Insert(ctx context.Context, repo *Ref) (*Ref, error) + Get(ctx context.Context, id uuid.UUID) (*Ref, error) +} + +var _ IRefRepo = (*RefRepo)(nil) + +type RefRepo struct { + *bun.DB +} + +func NewRefRepo(db *bun.DB) IRefRepo { + return &RefRepo{db} +} + +func (r RefRepo) Insert(ctx context.Context, ref *Ref) (*Ref, error) { + _, err := r.DB.NewInsert().Model(ref).Exec(ctx) + if err != nil { + return nil, err + } + return ref, nil +} + +func (r RefRepo) Get(ctx context.Context, id uuid.UUID) (*Ref, error) { + ref := &Ref{} + return ref, r.DB.NewSelect().Model(ref).Where("id = ?", id).Scan(ctx) +} diff --git a/models/ref_test.go b/models/ref_test.go new file mode 100644 index 00000000..8e78a565 --- /dev/null +++ b/models/ref_test.go @@ -0,0 +1,31 @@ +package models_test + +import ( + "context" + "testing" + + "github.com/brianvoe/gofakeit/v6" + "github.com/google/go-cmp/cmp" + "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/models" + "github.com/stretchr/testify/require" +) + +func TestRefRepo_Insert(t *testing.T) { + ctx := context.Background() + postgres, db := setup(ctx, t) + defer postgres.Stop() //nolint + + repo := models.NewRefRepo(db) + + refModel := &models.Ref{} + require.NoError(t, gofakeit.Struct(refModel)) + newRef, err := repo.Insert(ctx, refModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newRef.ID) + + ref, err := repo.Get(ctx, newRef.ID) + require.NoError(t, err) + + require.True(t, cmp.Equal(refModel, ref, dbTimeCmpOpt)) +} diff --git a/models/repository.go b/models/repository.go new file mode 100644 index 00000000..889efc12 --- /dev/null +++ b/models/repository.go @@ -0,0 +1,50 @@ +package models + +import ( + "context" + "time" + + "github.com/google/uuid" + "github.com/uptrace/bun" +) + +type Repository struct { + bun.BaseModel `bun:"table:repository"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` + Name string `bun:"name,notnull"` + StorageNamespace string `bun:"storage_namespace,notnull"` + Description string `bun:"description"` + HEAD uuid.UUID `bun:"head,type:uuid,notnull"` + CreateID uuid.UUID `bun:"create_id,type:uuid,notnull"` + + CreatedAt time.Time `bun:"created_at"` + UpdatedAt time.Time `bun:"updated_at"` +} + +type IRepositoryRepo interface { + Insert(ctx context.Context, repo *Repository) (*Repository, error) + Get(ctx context.Context, id uuid.UUID) (*Repository, error) +} + +var _ IRepositoryRepo = (*RepositoryRepo)(nil) + +type RepositoryRepo struct { + *bun.DB +} + +func NewRepositoryRepo(db *bun.DB) IRepositoryRepo { + return &RepositoryRepo{db} +} + +func (r *RepositoryRepo) Insert(ctx context.Context, repo *Repository) (*Repository, error) { + _, err := r.DB.NewInsert().Model(repo).Exec(ctx) + if err != nil { + return nil, err + } + return repo, nil +} + +func (r *RepositoryRepo) Get(ctx context.Context, id uuid.UUID) (*Repository, error) { + repo := &Repository{} + return repo, r.DB.NewSelect().Model(repo).Where("id = ?", id).Scan(ctx) +} diff --git a/models/repository_test.go b/models/repository_test.go new file mode 100644 index 00000000..3b2e3e72 --- /dev/null +++ b/models/repository_test.go @@ -0,0 +1,31 @@ +package models_test + +import ( + "context" + "testing" + + "github.com/brianvoe/gofakeit/v6" + "github.com/google/go-cmp/cmp" + "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/models" + "github.com/stretchr/testify/require" +) + +func TestRepositoryRepo_Insert(t *testing.T) { + ctx := context.Background() + postgres, db := setup(ctx, t) + defer postgres.Stop() //nolint + + repo := models.NewRepositoryRepo(db) + + repoModel := &models.Repository{} + require.NoError(t, gofakeit.Struct(repoModel)) + newUser, err := repo.Insert(ctx, repoModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newUser.ID) + + user, err := repo.Get(ctx, newUser.ID) + require.NoError(t, err) + + require.True(t, cmp.Equal(repoModel, user, dbTimeCmpOpt)) +} diff --git a/models/user.go b/models/user.go index aa8a6123..87996a0b 100644 --- a/models/user.go +++ b/models/user.go @@ -10,7 +10,7 @@ import ( ) type User struct { - bun.BaseModel `bun:"table:users,alias:u"` + bun.BaseModel `bun:"table:users"` ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` Name string `bun:"name,notnull"` Email string `bun:"email,notnull"` @@ -24,7 +24,7 @@ type User struct { } type IUserRepo interface { - GetUser(ctx context.Context, id uuid.UUID) (*User, error) + Get(ctx context.Context, id uuid.UUID) (*User, error) Insert(ctx context.Context, user *User) (*User, error) } @@ -38,7 +38,7 @@ func NewUserRepo(db *bun.DB) IUserRepo { return &UserRepo{db} } -func (userRepo *UserRepo) GetUser(ctx context.Context, id uuid.UUID) (*User, error) { +func (userRepo *UserRepo) Get(ctx context.Context, id uuid.UUID) (*User, error) { user := &User{} return user, userRepo.DB.NewSelect().Model(user).Where("id = ?", id).Scan(ctx) } diff --git a/models/user_test.go b/models/user_test.go index 2fb49a46..40468bd3 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -6,6 +6,8 @@ import ( "testing" "time" + "github.com/brianvoe/gofakeit/v6" + "github.com/google/go-cmp/cmp" "github.com/google/uuid" @@ -43,28 +45,21 @@ func setup(ctx context.Context, t *testing.T) (*embeddedpostgres.EmbeddedPostgre require.NoError(t, err) return postgres, db } + func TestNewUserRepo(t *testing.T) { ctx := context.Background() postgres, db := setup(ctx, t) defer postgres.Stop() //nolint repo := models.NewUserRepo(db) - userModel := &models.User{ - Name: "aaa", - Email: "xx@gmail.com", - EncryptedPassword: "aaaaa", - CurrentSignInAt: time.Now(), - LastSignInAt: time.Now(), - CurrentSignInIP: "127.0.0.1", - LastSignInIP: "127.0.0.1", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } + + userModel := &models.User{} + require.NoError(t, gofakeit.Struct(userModel)) newUser, err := repo.Insert(ctx, userModel) require.NoError(t, err) require.NotEqual(t, uuid.Nil, newUser.ID) - user, err := repo.GetUser(ctx, newUser.ID) + user, err := repo.Get(ctx, newUser.ID) require.NoError(t, err) require.True(t, cmp.Equal(userModel.UpdatedAt, user.UpdatedAt, dbTimeCmpOpt)) diff --git a/utils/hash/hash.go b/utils/hash/hash.go new file mode 100644 index 00000000..51beb812 --- /dev/null +++ b/utils/hash/hash.go @@ -0,0 +1,62 @@ +// Package hash provides a way for managing the +// underlying hash implementations used across go-git. +package hash + +import ( + "crypto" + "fmt" + "hash" + + "github.com/pjbgf/sha1cd" +) + +// algos is a map of hash algorithms. +var algos = map[crypto.Hash]func() hash.Hash{} + +func init() { + reset() +} + +// reset resets the default algos value. Can be used after running tests +// that registers new algorithms to avoid side effects. +func reset() { + algos[crypto.SHA1] = sha1cd.New + algos[crypto.SHA256] = crypto.SHA256.New +} + +// RegisterHasher allows for the hash algorithm used to be overridden. +// This ensures the hash selection for go-git must be explicit, when +// overriding the default value. +func RegisterHasher(h crypto.Hash, f func() hash.Hash) error { + if f == nil { + return fmt.Errorf("cannot register hash: f is nil") + } + + switch h { + case crypto.SHA1: + algos[h] = f + case crypto.SHA256: + algos[h] = f + default: + return fmt.Errorf("unsupported hash function: %v", h) + } + return nil +} + +type Hash []byte + +// Hasher is the same as hash.Hash. This allows consumers +// to not having to import this package alongside "hash". +type Hasher interface { + hash.Hash +} + +// New returns a new Hash for the given hash function. +// It panics if the hash function is not registered. +func New(h crypto.Hash) Hasher { + hh, ok := algos[h] + if !ok { + panic(fmt.Sprintf("hash algorithm not registered: %v", h)) + } + return hh() +} diff --git a/utils/hash/hash_sha1.go b/utils/hash/hash_sha1.go new file mode 100644 index 00000000..e3cb60fe --- /dev/null +++ b/utils/hash/hash_sha1.go @@ -0,0 +1,15 @@ +//go:build !sha256 +// +build !sha256 + +package hash + +import "crypto" + +const ( + // CryptoType defines what hash algorithm is being used. + CryptoType = crypto.SHA1 + // Size defines the amount of bytes the hash yields. + Size = 20 + // HexSize defines the strings size of the hash when represented in hexadecimal. + HexSize = 40 +) diff --git a/utils/hash/hash_sha256.go b/utils/hash/hash_sha256.go new file mode 100644 index 00000000..1c52b897 --- /dev/null +++ b/utils/hash/hash_sha256.go @@ -0,0 +1,15 @@ +//go:build sha256 +// +build sha256 + +package hash + +import "crypto" + +const ( + // CryptoType defines what hash algorithm is being used. + CryptoType = crypto.SHA256 + // Size defines the amount of bytes the hash yields. + Size = 32 + // HexSize defines the strings size of the hash when represented in hexadecimal. + HexSize = 64 +) diff --git a/utils/hash/hash_test.go b/utils/hash/hash_test.go new file mode 100644 index 00000000..cb049921 --- /dev/null +++ b/utils/hash/hash_test.go @@ -0,0 +1,103 @@ +package hash + +import ( + "crypto" + "crypto/sha1" + "crypto/sha512" + "encoding/hex" + "hash" + "strings" + "testing" +) + +func TestRegisterHash(t *testing.T) { + // Reset default hash to avoid side effects. + defer reset() + + tests := []struct { + name string + hash crypto.Hash + new func() hash.Hash + wantErr string + }{ + { + name: "sha1", + hash: crypto.SHA1, + new: sha1.New, + }, + { + name: "sha1", + hash: crypto.SHA1, + wantErr: "cannot register hash: f is nil", + }, + { + name: "sha512", + hash: crypto.SHA512, + new: sha512.New, + wantErr: "unsupported hash function", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := RegisterHasher(tt.hash, tt.new) + if tt.wantErr == "" && err != nil { + t.Errorf("unexpected error: %v", err) + } else if tt.wantErr != "" && err == nil { + t.Errorf("expected error: %v got: nil", tt.wantErr) + } else if err != nil && !strings.Contains(err.Error(), tt.wantErr) { + t.Errorf("expected error: %v got: %v", tt.wantErr, err) + } + }) + } +} + +// Verifies that the SHA1 implementation used is collision-resistant +// by default. +func TestSha1Collision(t *testing.T) { + defer reset() + + tests := []struct { + name string + content string + hash string + before func() + }{ + { + name: "sha-mbles-1: with collision detection", + content: "99040d047fe81780012000ff4b65792069732070617274206f66206120636f6c6c6973696f6e212049742773206120747261702179c61af0afcc054515d9274e7307624b1dc7fb23988bb8de8b575dba7b9eab31c1674b6d974378a827732ff5851c76a2e60772b5a47ce1eac40bb993c12d8c70e24a4f8d5fcdedc1b32c9cf19e31af2429759d42e4dfdb31719f587623ee552939b6dcdc459fca53553b70f87ede30a247ea3af6c759a2f20b320d760db64ff479084fd3ccb3cdd48362d96a9c430617caff6c36c637e53fde28417f626fec54ed7943a46e5f5730f2bb38fb1df6e0090010d00e24ad78bf92641993608e8d158a789f34c46fe1e6027f35a4cbfb827076c50eca0e8b7cca69bb2c2b790259f9bf9570dd8d4437a3115faff7c3cac09ad25266055c27104755178eaeff825a2caa2acfb5de64ce7641dc59a541a9fc9c756756e2e23dc713c8c24c9790aa6b0e38a7f55f14452a1ca2850ddd9562fd9a18ad42496aa97008f74672f68ef461eb88b09933d626b4f918749cc027fddd6c425fc4216835d0134d15285bab2cb784a4f7cbb4fb514d4bf0f6237cf00a9e9f132b9a066e6fd17f6c42987478586ff651af96747fb426b9872b9a88e4063f59bb334cc00650f83a80c42751b71974d300fc2819a2e8f1e32c1b51cb18e6bfc4db9baef675d4aaf5b1574a047f8f6dd2ec153a93412293974d928f88ced9363cfef97ce2e742bf34c96b8ef3875676fea5cca8e5f7dea0bab2413d4de00ee71ee01f162bdb6d1eafd925e6aebaae6a354ef17cf205a404fbdb12fc454d41fdd95cf2459664a2ad032d1da60a73264075d7f1e0d6c1403ae7a0d861df3fe5707188dd5e07d1589b9f8b6630553f8fc352b3e0c27da80bddba4c64020d", + hash: "4f3d9be4a472c4dae83c6314aa6c36a064c1fd14", + }, + { + name: "sha-mbles-1: with default SHA1", + content: "99040d047fe81780012000ff4b65792069732070617274206f66206120636f6c6c6973696f6e212049742773206120747261702179c61af0afcc054515d9274e7307624b1dc7fb23988bb8de8b575dba7b9eab31c1674b6d974378a827732ff5851c76a2e60772b5a47ce1eac40bb993c12d8c70e24a4f8d5fcdedc1b32c9cf19e31af2429759d42e4dfdb31719f587623ee552939b6dcdc459fca53553b70f87ede30a247ea3af6c759a2f20b320d760db64ff479084fd3ccb3cdd48362d96a9c430617caff6c36c637e53fde28417f626fec54ed7943a46e5f5730f2bb38fb1df6e0090010d00e24ad78bf92641993608e8d158a789f34c46fe1e6027f35a4cbfb827076c50eca0e8b7cca69bb2c2b790259f9bf9570dd8d4437a3115faff7c3cac09ad25266055c27104755178eaeff825a2caa2acfb5de64ce7641dc59a541a9fc9c756756e2e23dc713c8c24c9790aa6b0e38a7f55f14452a1ca2850ddd9562fd9a18ad42496aa97008f74672f68ef461eb88b09933d626b4f918749cc027fddd6c425fc4216835d0134d15285bab2cb784a4f7cbb4fb514d4bf0f6237cf00a9e9f132b9a066e6fd17f6c42987478586ff651af96747fb426b9872b9a88e4063f59bb334cc00650f83a80c42751b71974d300fc2819a2e8f1e32c1b51cb18e6bfc4db9baef675d4aaf5b1574a047f8f6dd2ec153a93412293974d928f88ced9363cfef97ce2e742bf34c96b8ef3875676fea5cca8e5f7dea0bab2413d4de00ee71ee01f162bdb6d1eafd925e6aebaae6a354ef17cf205a404fbdb12fc454d41fdd95cf2459664a2ad032d1da60a73264075d7f1e0d6c1403ae7a0d861df3fe5707188dd5e07d1589b9f8b6630553f8fc352b3e0c27da80bddba4c64020d", + hash: "8ac60ba76f1999a1ab70223f225aefdc78d4ddc0", + before: func() { + _ = RegisterHasher(crypto.SHA1, sha1.New) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.before != nil { + tt.before() + } + + h := New(crypto.SHA1) + data, err := hex.DecodeString(tt.content) + if err != nil { + t.Fatal(err) + } + + h.Reset() + h.Write(data) + sum := h.Sum(nil) + got := hex.EncodeToString(sum) + + if tt.hash != got { + t.Errorf("\n got: %q\nwanted: %q", got, tt.hash) + } + }) + } +} From 06d1b6eaa4eeb8536c852f5df3bee751b4e77763 Mon Sep 17 00:00:00 2001 From: zjy Date: Fri, 1 Dec 2023 18:18:24 +0800 Subject: [PATCH 028/210] feat: add user login --- api/api_impl/impl.go | 1 + api/jiaozifs.gen.go | 249 ++++++++++++++++++++++++++++++++++++----- api/swagger.yml | 50 +++++++++ auth/basic_auth.go | 51 +++++++++ auth/jwt_login.go | 27 +++++ auth/types.go | 7 ++ config/config.go | 5 + config/default.go | 3 + controller/user_ctl.go | 39 +++++++ go.mod | 15 +-- go.sum | 26 +++-- models/user.go | 11 ++ models/user_test.go | 4 + 13 files changed, 438 insertions(+), 50 deletions(-) create mode 100644 auth/jwt_login.go create mode 100644 auth/types.go create mode 100644 controller/user_ctl.go diff --git a/api/api_impl/impl.go b/api/api_impl/impl.go index 1f759ae4..060b6d4f 100644 --- a/api/api_impl/impl.go +++ b/api/api_impl/impl.go @@ -13,4 +13,5 @@ type APIController struct { controller.VersionController controller.ObjectController + controller.UserController } diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index eb5e6000..32c92095 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -33,6 +33,15 @@ const ( Object ObjectStatsPathType = "object" ) +// AuthenticationToken defines model for AuthenticationToken. +type AuthenticationToken struct { + // Token a JWT token that could be used to authenticate requests + Token string `json:"token"` + + // TokenExpiration Unix Epoch in seconds + TokenExpiration *int64 `json:"token_expiration,omitempty"` +} + // ObjectStats defines model for ObjectStats. type ObjectStats struct { Checksum string `json:"checksum"` @@ -121,9 +130,18 @@ type UploadObjectParams struct { IfNoneMatch *string `json:"If-None-Match,omitempty"` } +// LoginJSONBody defines parameters for Login. +type LoginJSONBody struct { + Password string `json:"password"` + Username string `json:"username"` +} + // UploadObjectMultipartRequestBody defines body for UploadObject for multipart/form-data ContentType. type UploadObjectMultipartRequestBody UploadObjectMultipartBody +// LoginJSONRequestBody defines body for Login for application/json ContentType. +type LoginJSONRequestBody LoginJSONBody + // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error @@ -209,6 +227,11 @@ type ClientInterface interface { // UploadObjectWithBody request with any body UploadObjectWithBody(ctx context.Context, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + // LoginWithBody request with any body + LoginWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + Login(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetVersion request GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) } @@ -261,6 +284,30 @@ func (c *Client) UploadObjectWithBody(ctx context.Context, repository string, pa return c.Client.Do(req) } +func (c *Client) LoginWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewLoginRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) Login(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewLoginRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewGetVersionRequest(c.Server) if err != nil { @@ -560,6 +607,46 @@ func NewUploadObjectRequestWithBody(server string, repository string, params *Up return req, nil } +// NewLoginRequest calls the generic Login builder with application/json body +func NewLoginRequest(server string, body LoginJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewLoginRequestWithBody(server, "application/json", bodyReader) +} + +// NewLoginRequestWithBody generates requests for Login with any type of body +func NewLoginRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/user/login") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewGetVersionRequest generates requests for GetVersion func NewGetVersionRequest(server string) (*http.Request, error) { var err error @@ -642,6 +729,11 @@ type ClientWithResponsesInterface interface { // UploadObjectWithBodyWithResponse request with any body UploadObjectWithBodyWithResponse(ctx context.Context, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) + // LoginWithBodyWithResponse request with any body + LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) + + LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) + // GetVersionWithResponse request GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) } @@ -731,6 +823,28 @@ func (r UploadObjectResponse) StatusCode() int { return 0 } +type LoginResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *AuthenticationToken +} + +// Status returns HTTPResponse.Status +func (r LoginResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r LoginResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type GetVersionResponse struct { Body []byte HTTPResponse *http.Response @@ -789,6 +903,23 @@ func (c *ClientWithResponses) UploadObjectWithBodyWithResponse(ctx context.Conte return ParseUploadObjectResponse(rsp) } +// LoginWithBodyWithResponse request with arbitrary body returning *LoginResponse +func (c *ClientWithResponses) LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) { + rsp, err := c.LoginWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseLoginResponse(rsp) +} + +func (c *ClientWithResponses) LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) { + rsp, err := c.Login(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseLoginResponse(rsp) +} + // GetVersionWithResponse request returning *GetVersionResponse func (c *ClientWithResponses) GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) { rsp, err := c.GetVersion(ctx, reqEditors...) @@ -872,6 +1003,32 @@ func ParseUploadObjectResponse(rsp *http.Response) (*UploadObjectResponse, error return response, nil } +// ParseLoginResponse parses an HTTP response from a LoginWithResponse call +func ParseLoginResponse(rsp *http.Response) (*LoginResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &LoginResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest AuthenticationToken + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseGetVersionResponse parses an HTTP response from a GetVersionWithResponse call func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -912,6 +1069,9 @@ type ServerInterface interface { // (POST /repositories/{repository}/objects) UploadObject(w *JiaozifsResponse, r *http.Request, repository string, params UploadObjectParams) + // perform a login + // (POST /user/login) + Login(w *JiaozifsResponse, r *http.Request) // return program and runtime version // (GET /version) GetVersion(w *JiaozifsResponse, r *http.Request) @@ -944,6 +1104,12 @@ func (_ Unimplemented) UploadObject(w *JiaozifsResponse, r *http.Request, reposi w.WriteHeader(http.StatusNotImplemented) } +// perform a login +// (POST /user/login) +func (_ Unimplemented) Login(w *JiaozifsResponse, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + // return program and runtime version // (GET /version) func (_ Unimplemented) GetVersion(w *JiaozifsResponse, r *http.Request) { @@ -1230,6 +1396,21 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R handler.ServeHTTP(w, r.WithContext(ctx)) } +// Login operation middleware +func (siw *ServerInterfaceWrapper) Login(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.Login(&JiaozifsResponse{w}, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // GetVersion operation middleware func (siw *ServerInterfaceWrapper) GetVersion(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -1372,6 +1553,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/repositories/{repository}/objects", wrapper.UploadObject) }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/user/login", wrapper.Login) + }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/version", wrapper.GetVersion) }) @@ -1382,37 +1566,40 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "Gus4chqDEeO7Q764Wfw7AAD//58N5ajGGQAA", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 1ab95d18..579eea80 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -37,6 +37,18 @@ components: in: cookie name: saml_auth_session schemas: + AuthenticationToken: + type: object + required: + - token + properties: + token: + description: a JWT token that could be used to authenticate requests + type: string + token_expiration: + type: integer + format: int64 + description: Unix Epoch in seconds VersionResult: type: object required: @@ -379,3 +391,41 @@ paths: description: NotFound 420: description: too many requests + /user/login: + post: + tags: + - user + operationId: login + summary: perform a login + security: [] # No authentication + requestBody: + content: + application/json: + schema: + type: object + required: + - username + - password + properties: + username: + type: string + password: + type: string + responses: + 200: + description: successful login + headers: + Set-Cookie: + schema: + type: string + example: "access_token=abcde12356; Path=/; HttpOnly" + content: + application/json: + schema: + $ref: "#/components/schemas/AuthenticationToken" + 401: + description: Unauthorized ValidationError + 420: + description: too many requests + default: + description: Internal Server Error \ No newline at end of file diff --git a/auth/basic_auth.go b/auth/basic_auth.go index 8832b06d..b5f83089 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -1 +1,52 @@ package auth + +import ( + "context" + "time" + + "github.com/go-openapi/swag" + logging "github.com/ipfs/go-log/v2" + "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/config" + "github.com/jiaozifs/jiaozifs/models" + "golang.org/x/crypto/bcrypt" +) + +var log = logging.Logger("auth") + +type Login struct { + Username string `json:"username"` + Password string `json:"password"` +} + +func (l *Login) Login(userRepo models.IUserRepo, config *config.Config) (token api.AuthenticationToken, err error) { + ctx := context.Background() + // Get user encryptedPassword by username + ep, err := userRepo.GetEPByName(ctx, l.Username) + if err != nil { + log.Errorf("username err: %s", err) + return token, err + } + + // Compare ep and password + err = bcrypt.CompareHashAndPassword([]byte(ep), []byte(l.Password)) + if err != nil { + log.Errorf("password err: %s", err) + return token, err + } + // Generate user token + loginTime := time.Now() + expires := loginTime.Add(expirationDuration) + secretKey := config.Auth.SecretKey + + tokenString, err := GenerateJWTLogin(secretKey, l.Username, loginTime, expires) + if err != nil { + log.Errorf("generate token err: %s", err) + return token, err + } + + token.Token = tokenString + token.TokenExpiration = swag.Int64(expires.Unix()) + + return token, nil +} diff --git a/auth/jwt_login.go b/auth/jwt_login.go new file mode 100644 index 00000000..e1373f6a --- /dev/null +++ b/auth/jwt_login.go @@ -0,0 +1,27 @@ +package auth + +import ( + "time" + + "github.com/golang-jwt/jwt" + "github.com/google/uuid" +) + +const ( + LoginAudience = "login" +) + +// GenerateJWTLogin creates a jwt token which can be used for authentication during login only, i.e. it will not work for password reset. +// It supports backward compatibility for creating a login jwt. The audience is not set for login token. Any audience will make the token +// invalid for login. No email is passed to support the ability of login for users via user/access keys which don't have an email yet +func GenerateJWTLogin(secret []byte, userID string, issuedAt, expiresAt time.Time) (string, error) { + claims := &jwt.StandardClaims{ + Id: uuid.NewString(), + Audience: LoginAudience, + Subject: userID, + IssuedAt: issuedAt.Unix(), + ExpiresAt: expiresAt.Unix(), + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString(secret) +} diff --git a/auth/types.go b/auth/types.go new file mode 100644 index 00000000..aaea0caf --- /dev/null +++ b/auth/types.go @@ -0,0 +1,7 @@ +package auth + +import "time" + +const ( + expirationDuration = time.Hour +) diff --git a/config/config.go b/config/config.go index 0f034e2c..5494619f 100644 --- a/config/config.go +++ b/config/config.go @@ -15,6 +15,7 @@ type Config struct { Log LogConfig `mapstructure:"log"` API APIConfig `mapstructure:"api"` Database DatabaseConfig `mapstructure:"database"` + Auth AuthConfig `mapstructure:"auth"` Blockstore BlockStoreConfig `mapstructure:"blockstore"` } @@ -32,6 +33,10 @@ type DatabaseConfig struct { Debug bool `mapstructure:"debug"` } +type AuthConfig struct { + SecretKey []byte `mapstructure:"secretKey"` +} + func InitConfig() error { // Find home directory. home, err := os.UserHomeDir() diff --git a/config/default.go b/config/default.go index 47d6d8bf..b1709816 100644 --- a/config/default.go +++ b/config/default.go @@ -25,4 +25,7 @@ var defaultCfg = Config{ AllowedExternalPrefixes []string }{Path: DefaultLocalBSPath, ImportEnabled: false, ImportHidden: false, AllowedExternalPrefixes: nil}), }, + Auth: AuthConfig{ + SecretKey: []byte("THIS_MUST_BE_CHANGED_IN_PRODUCTION"), + }, } diff --git a/controller/user_ctl.go b/controller/user_ctl.go new file mode 100644 index 00000000..3dfb6bcd --- /dev/null +++ b/controller/user_ctl.go @@ -0,0 +1,39 @@ +package controller + +import ( + "encoding/json" + "net/http" + + "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/auth" + "github.com/jiaozifs/jiaozifs/config" + "github.com/jiaozifs/jiaozifs/models" + "go.uber.org/fx" +) + +type UserController struct { + fx.In + + UserRepo models.IUserRepo + Config *config.Config +} + +func (A UserController) Login(w *api.JiaozifsResponse, r *http.Request) { + // Decode requestBody + var login auth.Login + decoder := json.NewDecoder(r.Body) + if err := decoder.Decode(&login); err != nil { + w.RespError(err) + return + } + + // Perform login + resp, err := login.Login(A.UserRepo, A.Config) + if err != nil { + w.RespError(err) + return + } + + // resp + w.RespJSON(resp) +} diff --git a/go.mod b/go.mod index 9147bc05..295405ca 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,9 @@ require ( github.com/getkin/kin-openapi v0.118.0 github.com/go-chi/chi/v5 v5.0.10 github.com/go-chi/cors v1.2.1 + github.com/go-openapi/swag v0.22.4 github.com/go-test/deep v1.1.0 + github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.4.0 github.com/hnlq715/golang-lru v0.4.0 @@ -47,6 +49,7 @@ require ( github.com/uptrace/bun/driver/pgdriver v1.1.16 github.com/uptrace/bun/extra/bundebug v1.1.16 go.uber.org/fx v1.20.1 + golang.org/x/crypto v0.16.0 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 golang.org/x/oauth2 v0.13.0 google.golang.org/api v0.147.0 @@ -90,7 +93,6 @@ require ( github.com/fatih/color v1.15.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/swag v0.22.4 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v5 v5.0.0 // indirect @@ -158,13 +160,12 @@ require ( go.uber.org/dig v1.17.0 // indirect go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.23.0 // indirect - golang.org/x/crypto v0.14.0 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sync v0.4.0 // indirect - golang.org/x/sys v0.13.0 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/sync v0.5.0 // indirect + golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.13.0 // indirect + golang.org/x/tools v0.16.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect diff --git a/go.sum b/go.sum index 6e6f6997..5897a877 100644 --- a/go.sum +++ b/go.sum @@ -185,6 +185,8 @@ github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -497,8 +499,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -536,8 +538,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -572,8 +574,8 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -596,8 +598,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -643,8 +645,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -709,8 +711,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= +golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/models/user.go b/models/user.go index 87996a0b..b6132f65 100644 --- a/models/user.go +++ b/models/user.go @@ -26,6 +26,8 @@ type User struct { type IUserRepo interface { Get(ctx context.Context, id uuid.UUID) (*User, error) Insert(ctx context.Context, user *User) (*User, error) + + GetEPByName(ctx context.Context, name string) (string, error) } var _ IUserRepo = (*UserRepo)(nil) @@ -50,3 +52,12 @@ func (userRepo *UserRepo) Insert(ctx context.Context, user *User) (*User, error) } return user, nil } + +func (userRepo *UserRepo) GetEPByName(ctx context.Context, name string) (string, error) { + var ep string + return ep, userRepo.DB.NewSelect(). + Model((*User)(nil)).Column("encrypted_password"). + Where("name = ?", name). + Scan(ctx, &ep) + +} diff --git a/models/user_test.go b/models/user_test.go index 40468bd3..c0a6abd9 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -63,4 +63,8 @@ func TestNewUserRepo(t *testing.T) { require.NoError(t, err) require.True(t, cmp.Equal(userModel.UpdatedAt, user.UpdatedAt, dbTimeCmpOpt)) + + ep, err := repo.GetEPByName(ctx, newUser.Name) + require.NoError(t, err) + require.True(t, cmp.Equal(userModel.EncryptedPassword, ep)) } From 80f865cb6be0c91bac7d42f68da409036dea951a Mon Sep 17 00:00:00 2001 From: zjy Date: Fri, 1 Dec 2023 19:17:42 +0800 Subject: [PATCH 029/210] fix: fix type of userrepo --- controller/user_ctl.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/controller/user_ctl.go b/controller/user_ctl.go index 3dfb6bcd..f96af687 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -14,7 +14,7 @@ import ( type UserController struct { fx.In - UserRepo models.IUserRepo + UserRepo *models.IUserRepo Config *config.Config } @@ -28,7 +28,7 @@ func (A UserController) Login(w *api.JiaozifsResponse, r *http.Request) { } // Perform login - resp, err := login.Login(A.UserRepo, A.Config) + resp, err := login.Login(*A.UserRepo, A.Config) if err != nil { w.RespError(err) return From 3c6bef5f76eb26bcee0b46db49f3be2938123e1c Mon Sep 17 00:00:00 2001 From: zjy Date: Fri, 1 Dec 2023 19:28:23 +0800 Subject: [PATCH 030/210] fix: add end of line to swagger.yml --- api/swagger.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/swagger.yml b/api/swagger.yml index 579eea80..254b25f9 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -391,6 +391,7 @@ paths: description: NotFound 420: description: too many requests + /user/login: post: tags: @@ -428,4 +429,4 @@ paths: 420: description: too many requests default: - description: Internal Server Error \ No newline at end of file + description: Internal Server Error From 5789e7429a81c95b23e7b450625a58cd749df070 Mon Sep 17 00:00:00 2001 From: zjy Date: Mon, 4 Dec 2023 10:17:54 +0800 Subject: [PATCH 031/210] fix: update url name --- api/swagger.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/swagger.yml b/api/swagger.yml index 254b25f9..8d66d9a1 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -392,7 +392,7 @@ paths: 420: description: too many requests - /user/login: + /auth/login: post: tags: - user From 0a88031ce746476f251c23dafd12ad1b00178443 Mon Sep 17 00:00:00 2001 From: zjy Date: Mon, 4 Dec 2023 15:20:05 +0800 Subject: [PATCH 032/210] feat: add user registration --- api/jiaozifs.gen.go | 553 +++++++++++++++++++++++++++-------------- api/swagger.yml | 98 +++++++- auth/basic_auth.go | 41 +++ auth/types.go | 1 + controller/user_ctl.go | 17 ++ models/user.go | 9 + 6 files changed, 533 insertions(+), 186 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 32c92095..65344397 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -76,6 +76,18 @@ type ObjectStatsPathType string // ObjectUserMetadata defines model for ObjectUserMetadata. type ObjectUserMetadata map[string]string +// RegistrationMsg defines model for RegistrationMsg. +type RegistrationMsg struct { + Message string `json:"message"` +} + +// UserRegisterInfo defines model for UserRegisterInfo. +type UserRegisterInfo struct { + Email openapi_types.Email `json:"email"` + Password string `json:"password"` + Username string `json:"username"` +} + // VersionResult defines model for VersionResult. type VersionResult struct { // ApiVersion runtime version @@ -85,6 +97,12 @@ type VersionResult struct { Version string `json:"version"` } +// LoginJSONBody defines parameters for Login. +type LoginJSONBody struct { + Password string `json:"password"` + Username string `json:"username"` +} + // DeleteObjectParams defines parameters for DeleteObject. type DeleteObjectParams struct { // Path relative to the ref @@ -130,18 +148,15 @@ type UploadObjectParams struct { IfNoneMatch *string `json:"If-None-Match,omitempty"` } -// LoginJSONBody defines parameters for Login. -type LoginJSONBody struct { - Password string `json:"password"` - Username string `json:"username"` -} +// LoginJSONRequestBody defines body for Login for application/json ContentType. +type LoginJSONRequestBody LoginJSONBody + +// RegisterJSONRequestBody defines body for Register for application/json ContentType. +type RegisterJSONRequestBody = UserRegisterInfo // UploadObjectMultipartRequestBody defines body for UploadObject for multipart/form-data ContentType. type UploadObjectMultipartRequestBody UploadObjectMultipartBody -// LoginJSONRequestBody defines body for Login for application/json ContentType. -type LoginJSONRequestBody LoginJSONBody - // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error @@ -215,6 +230,16 @@ func WithRequestEditorFn(fn RequestEditorFn) ClientOption { // The interface specification for the client above. type ClientInterface interface { + // LoginWithBody request with any body + LoginWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + Login(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // RegisterWithBody request with any body + RegisterWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + Register(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // DeleteObject request DeleteObject(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -227,17 +252,12 @@ type ClientInterface interface { // UploadObjectWithBody request with any body UploadObjectWithBody(ctx context.Context, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - // LoginWithBody request with any body - LoginWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - Login(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // GetVersion request GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) } -func (c *Client) DeleteObject(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteObjectRequest(c.Server, repository, params) +func (c *Client) LoginWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewLoginRequestWithBody(c.Server, contentType, body) if err != nil { return nil, err } @@ -248,8 +268,8 @@ func (c *Client) DeleteObject(ctx context.Context, repository string, params *De return c.Client.Do(req) } -func (c *Client) GetObject(ctx context.Context, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetObjectRequest(c.Server, repository, params) +func (c *Client) Login(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewLoginRequest(c.Server, body) if err != nil { return nil, err } @@ -260,8 +280,8 @@ func (c *Client) GetObject(ctx context.Context, repository string, params *GetOb return c.Client.Do(req) } -func (c *Client) HeadObject(ctx context.Context, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewHeadObjectRequest(c.Server, repository, params) +func (c *Client) RegisterWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRegisterRequestWithBody(c.Server, contentType, body) if err != nil { return nil, err } @@ -272,8 +292,8 @@ func (c *Client) HeadObject(ctx context.Context, repository string, params *Head return c.Client.Do(req) } -func (c *Client) UploadObjectWithBody(ctx context.Context, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewUploadObjectRequestWithBody(c.Server, repository, params, contentType, body) +func (c *Client) Register(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRegisterRequest(c.Server, body) if err != nil { return nil, err } @@ -284,8 +304,8 @@ func (c *Client) UploadObjectWithBody(ctx context.Context, repository string, pa return c.Client.Do(req) } -func (c *Client) LoginWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewLoginRequestWithBody(c.Server, contentType, body) +func (c *Client) DeleteObject(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteObjectRequest(c.Server, repository, params) if err != nil { return nil, err } @@ -296,8 +316,32 @@ func (c *Client) LoginWithBody(ctx context.Context, contentType string, body io. return c.Client.Do(req) } -func (c *Client) Login(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewLoginRequest(c.Server, body) +func (c *Client) GetObject(ctx context.Context, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetObjectRequest(c.Server, repository, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) HeadObject(ctx context.Context, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewHeadObjectRequest(c.Server, repository, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UploadObjectWithBody(ctx context.Context, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUploadObjectRequestWithBody(c.Server, repository, params, contentType, body) if err != nil { return nil, err } @@ -320,6 +364,86 @@ func (c *Client) GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) return c.Client.Do(req) } +// NewLoginRequest calls the generic Login builder with application/json body +func NewLoginRequest(server string, body LoginJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewLoginRequestWithBody(server, "application/json", bodyReader) +} + +// NewLoginRequestWithBody generates requests for Login with any type of body +func NewLoginRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/auth/login") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewRegisterRequest calls the generic Register builder with application/json body +func NewRegisterRequest(server string, body RegisterJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewRegisterRequestWithBody(server, "application/json", bodyReader) +} + +// NewRegisterRequestWithBody generates requests for Register with any type of body +func NewRegisterRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/auth/register") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewDeleteObjectRequest generates requests for DeleteObject func NewDeleteObjectRequest(server string, repository string, params *DeleteObjectParams) (*http.Request, error) { var err error @@ -607,46 +731,6 @@ func NewUploadObjectRequestWithBody(server string, repository string, params *Up return req, nil } -// NewLoginRequest calls the generic Login builder with application/json body -func NewLoginRequest(server string, body LoginJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewLoginRequestWithBody(server, "application/json", bodyReader) -} - -// NewLoginRequestWithBody generates requests for Login with any type of body -func NewLoginRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/user/login") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("POST", queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - // NewGetVersionRequest generates requests for GetVersion func NewGetVersionRequest(server string) (*http.Request, error) { var err error @@ -717,6 +801,16 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { + // LoginWithBodyWithResponse request with any body + LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) + + LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) + + // RegisterWithBodyWithResponse request with any body + RegisterWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterResponse, error) + + RegisterWithResponse(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterResponse, error) + // DeleteObjectWithResponse request DeleteObjectWithResponse(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) @@ -729,22 +823,18 @@ type ClientWithResponsesInterface interface { // UploadObjectWithBodyWithResponse request with any body UploadObjectWithBodyWithResponse(ctx context.Context, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) - // LoginWithBodyWithResponse request with any body - LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) - - LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) - // GetVersionWithResponse request GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) } -type DeleteObjectResponse struct { +type LoginResponse struct { Body []byte HTTPResponse *http.Response + JSON200 *AuthenticationToken } // Status returns HTTPResponse.Status -func (r DeleteObjectResponse) Status() string { +func (r LoginResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -752,20 +842,21 @@ func (r DeleteObjectResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r DeleteObjectResponse) StatusCode() int { +func (r LoginResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetObjectResponse struct { +type RegisterResponse struct { Body []byte HTTPResponse *http.Response + JSON201 *RegistrationMsg } // Status returns HTTPResponse.Status -func (r GetObjectResponse) Status() string { +func (r RegisterResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -773,20 +864,20 @@ func (r GetObjectResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetObjectResponse) StatusCode() int { +func (r RegisterResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type HeadObjectResponse struct { +type DeleteObjectResponse struct { Body []byte HTTPResponse *http.Response } // Status returns HTTPResponse.Status -func (r HeadObjectResponse) Status() string { +func (r DeleteObjectResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -794,21 +885,20 @@ func (r HeadObjectResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r HeadObjectResponse) StatusCode() int { +func (r DeleteObjectResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type UploadObjectResponse struct { +type GetObjectResponse struct { Body []byte HTTPResponse *http.Response - JSON201 *ObjectStats } // Status returns HTTPResponse.Status -func (r UploadObjectResponse) Status() string { +func (r GetObjectResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -816,21 +906,20 @@ func (r UploadObjectResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r UploadObjectResponse) StatusCode() int { +func (r GetObjectResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type LoginResponse struct { +type HeadObjectResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *AuthenticationToken } // Status returns HTTPResponse.Status -func (r LoginResponse) Status() string { +func (r HeadObjectResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -838,7 +927,29 @@ func (r LoginResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r LoginResponse) StatusCode() int { +func (r HeadObjectResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type UploadObjectResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *ObjectStats +} + +// Status returns HTTPResponse.Status +func (r UploadObjectResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UploadObjectResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -867,6 +978,40 @@ func (r GetVersionResponse) StatusCode() int { return 0 } +// LoginWithBodyWithResponse request with arbitrary body returning *LoginResponse +func (c *ClientWithResponses) LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) { + rsp, err := c.LoginWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseLoginResponse(rsp) +} + +func (c *ClientWithResponses) LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) { + rsp, err := c.Login(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseLoginResponse(rsp) +} + +// RegisterWithBodyWithResponse request with arbitrary body returning *RegisterResponse +func (c *ClientWithResponses) RegisterWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterResponse, error) { + rsp, err := c.RegisterWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseRegisterResponse(rsp) +} + +func (c *ClientWithResponses) RegisterWithResponse(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterResponse, error) { + rsp, err := c.Register(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseRegisterResponse(rsp) +} + // DeleteObjectWithResponse request returning *DeleteObjectResponse func (c *ClientWithResponses) DeleteObjectWithResponse(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) { rsp, err := c.DeleteObject(ctx, repository, params, reqEditors...) @@ -903,30 +1048,65 @@ func (c *ClientWithResponses) UploadObjectWithBodyWithResponse(ctx context.Conte return ParseUploadObjectResponse(rsp) } -// LoginWithBodyWithResponse request with arbitrary body returning *LoginResponse -func (c *ClientWithResponses) LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) { - rsp, err := c.LoginWithBody(ctx, contentType, body, reqEditors...) +// GetVersionWithResponse request returning *GetVersionResponse +func (c *ClientWithResponses) GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) { + rsp, err := c.GetVersion(ctx, reqEditors...) if err != nil { return nil, err } - return ParseLoginResponse(rsp) + return ParseGetVersionResponse(rsp) } -func (c *ClientWithResponses) LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) { - rsp, err := c.Login(ctx, body, reqEditors...) +// ParseLoginResponse parses an HTTP response from a LoginWithResponse call +func ParseLoginResponse(rsp *http.Response) (*LoginResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - return ParseLoginResponse(rsp) + + response := &LoginResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest AuthenticationToken + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil } -// GetVersionWithResponse request returning *GetVersionResponse -func (c *ClientWithResponses) GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) { - rsp, err := c.GetVersion(ctx, reqEditors...) +// ParseRegisterResponse parses an HTTP response from a RegisterWithResponse call +func ParseRegisterResponse(rsp *http.Response) (*RegisterResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - return ParseGetVersionResponse(rsp) + + response := &RegisterResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest RegistrationMsg + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil } // ParseDeleteObjectResponse parses an HTTP response from a DeleteObjectWithResponse call @@ -1003,32 +1183,6 @@ func ParseUploadObjectResponse(rsp *http.Response) (*UploadObjectResponse, error return response, nil } -// ParseLoginResponse parses an HTTP response from a LoginWithResponse call -func ParseLoginResponse(rsp *http.Response) (*LoginResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &LoginResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest AuthenticationToken - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - } - - return response, nil -} - // ParseGetVersionResponse parses an HTTP response from a GetVersionWithResponse call func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -1057,6 +1211,12 @@ func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) { // ServerInterface represents all server handlers. type ServerInterface interface { + // perform a login + // (POST /auth/login) + Login(w *JiaozifsResponse, r *http.Request) + // perform user registration + // (POST /auth/register) + Register(w *JiaozifsResponse, r *http.Request) // delete object. Missing objects will not return a NotFound error. // (DELETE /repositories/{repository}/objects) DeleteObject(w *JiaozifsResponse, r *http.Request, repository string, params DeleteObjectParams) @@ -1069,9 +1229,6 @@ type ServerInterface interface { // (POST /repositories/{repository}/objects) UploadObject(w *JiaozifsResponse, r *http.Request, repository string, params UploadObjectParams) - // perform a login - // (POST /user/login) - Login(w *JiaozifsResponse, r *http.Request) // return program and runtime version // (GET /version) GetVersion(w *JiaozifsResponse, r *http.Request) @@ -1081,6 +1238,18 @@ type ServerInterface interface { type Unimplemented struct{} +// perform a login +// (POST /auth/login) +func (_ Unimplemented) Login(w *JiaozifsResponse, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// perform user registration +// (POST /auth/register) +func (_ Unimplemented) Register(w *JiaozifsResponse, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + // delete object. Missing objects will not return a NotFound error. // (DELETE /repositories/{repository}/objects) func (_ Unimplemented) DeleteObject(w *JiaozifsResponse, r *http.Request, repository string, params DeleteObjectParams) { @@ -1104,12 +1273,6 @@ func (_ Unimplemented) UploadObject(w *JiaozifsResponse, r *http.Request, reposi w.WriteHeader(http.StatusNotImplemented) } -// perform a login -// (POST /user/login) -func (_ Unimplemented) Login(w *JiaozifsResponse, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) -} - // return program and runtime version // (GET /version) func (_ Unimplemented) GetVersion(w *JiaozifsResponse, r *http.Request) { @@ -1125,6 +1288,36 @@ type ServerInterfaceWrapper struct { type MiddlewareFunc func(http.Handler) http.Handler +// Login operation middleware +func (siw *ServerInterfaceWrapper) Login(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.Login(&JiaozifsResponse{w}, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// Register operation middleware +func (siw *ServerInterfaceWrapper) Register(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.Register(&JiaozifsResponse{w}, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // DeleteObject operation middleware func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -1396,21 +1589,6 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R handler.ServeHTTP(w, r.WithContext(ctx)) } -// Login operation middleware -func (siw *ServerInterfaceWrapper) Login(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.Login(&JiaozifsResponse{w}, r) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r.WithContext(ctx)) -} - // GetVersion operation middleware func (siw *ServerInterfaceWrapper) GetVersion(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -1541,6 +1719,12 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl ErrorHandlerFunc: options.ErrorHandlerFunc, } + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/auth/login", wrapper.Login) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/auth/register", wrapper.Register) + }) r.Group(func(r chi.Router) { r.Delete(options.BaseURL+"/repositories/{repository}/objects", wrapper.DeleteObject) }) @@ -1553,9 +1737,6 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/repositories/{repository}/objects", wrapper.UploadObject) }) - r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/user/login", wrapper.Login) - }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/version", wrapper.GetVersion) }) @@ -1566,40 +1747,42 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 8d66d9a1..05bb2740 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -37,6 +37,75 @@ components: in: cookie name: saml_auth_session schemas: + UserUpdate: + type: object + required: + - username + - email + properties: + username: + type: string + email: + type: string + format: email + password: + type: string + minLength: 8 + + UserInfo: + type: object + required: + - username + - email + properties: + username: + type: string + email: + type: string + format: email + currentSignInAt: + type: string + format: date-time + lastSignInAt: + type: string + format: date-time + currentSignInIP: + type: string + format: ipv4 + lastSignInIP: + type: string + format: ipv4 + createdAt: + type: string + format: date-time + updateAt: + type: string + format: date-time + + RegistrationMsg: + type: object + required: + - message + properties: + message: + type: string + + UserRegisterInfo: + type: object + required: + - username + - email + - password + properties: + username: + type: string + password: + type: string + minLength: 8 + email: + type: string + format: email + AuthenticationToken: type: object required: @@ -49,6 +118,7 @@ components: type: integer format: int64 description: Unix Epoch in seconds + VersionResult: type: object required: @@ -395,7 +465,7 @@ paths: /auth/login: post: tags: - - user + - auth operationId: login summary: perform a login security: [] # No authentication @@ -430,3 +500,29 @@ paths: description: too many requests default: description: Internal Server Error + + /auth/register: + post: + tags: + - auth + operationId: register + summary: perform user registration + security: [] # No authentication + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/UserRegisterInfo" + responses: + 201: + description: registration success message + content: + application/json: + schema: + $ref: "#/components/schemas/RegistrationMsg" + 400: + description: Bad Request - Validation Error + 420: + description: too many requests + default: + description: Internal Server Error diff --git a/auth/basic_auth.go b/auth/basic_auth.go index b5f83089..99f77aad 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -18,6 +18,11 @@ type Login struct { Username string `json:"username"` Password string `json:"password"` } +type Register struct { + Username string `json:"username"` + Email string `json:"email"` + Password string `json:"password"` +} func (l *Login) Login(userRepo models.IUserRepo, config *config.Config) (token api.AuthenticationToken, err error) { ctx := context.Background() @@ -50,3 +55,39 @@ func (l *Login) Login(userRepo models.IUserRepo, config *config.Config) (token a return token, nil } + +func (l *Register) Register(userRepo models.IUserRepo) (msg api.RegistrationMsg, err error) { + ctx := context.Background() + // check username, email + if userRepo.CheckUserByNameEmail(ctx, l.Username, l.Email) { + msg.Message = "The username or email has already been registered" + return + } + + password, err := bcrypt.GenerateFromPassword([]byte(l.Password), passwordCost) + if err != nil { + msg.Message = "Generate Password err" + return + } + + // insert db + user := &models.User{ + Name: l.Username, + Email: l.Email, + EncryptedPassword: string(password), + CurrentSignInAt: time.Time{}, + LastSignInAt: time.Time{}, + CurrentSignInIP: "", + LastSignInIP: "", + CreatedAt: time.Now(), + UpdatedAt: time.Time{}, + } + insertUser, err := userRepo.Insert(ctx, user) + if err != nil { + msg.Message = "register user err" + return + } + // return + msg.Message = insertUser.Name + " register success" + return msg, nil +} diff --git a/auth/types.go b/auth/types.go index aaea0caf..badea89f 100644 --- a/auth/types.go +++ b/auth/types.go @@ -4,4 +4,5 @@ import "time" const ( expirationDuration = time.Hour + passwordCost = 12 ) diff --git a/controller/user_ctl.go b/controller/user_ctl.go index f96af687..02be39b3 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -37,3 +37,20 @@ func (A UserController) Login(w *api.JiaozifsResponse, r *http.Request) { // resp w.RespJSON(resp) } + +func (A UserController) Register(w *api.JiaozifsResponse, r *http.Request) { + // Decode requestBody + var register auth.Register + decoder := json.NewDecoder(r.Body) + if err := decoder.Decode(®ister); err != nil { + w.RespError(err) + } + // Perform register + msg, err := register.Register(*A.UserRepo) + if err != nil { + w.RespError(err) + return + } + // resp + w.RespJSON(msg) +} diff --git a/models/user.go b/models/user.go index b6132f65..2b9c87ec 100644 --- a/models/user.go +++ b/models/user.go @@ -28,6 +28,7 @@ type IUserRepo interface { Insert(ctx context.Context, user *User) (*User, error) GetEPByName(ctx context.Context, name string) (string, error) + CheckUserByNameEmail(ctx context.Context, name, email string) bool } var _ IUserRepo = (*UserRepo)(nil) @@ -59,5 +60,13 @@ func (userRepo *UserRepo) GetEPByName(ctx context.Context, name string) (string, Model((*User)(nil)).Column("encrypted_password"). Where("name = ?", name). Scan(ctx, &ep) +} +func (userRepo *UserRepo) CheckUserByNameEmail(ctx context.Context, name, email string) bool { + err := userRepo.DB.NewSelect().Model((*User)(nil)).Where("name = ? OR email = ?", name, email). + Limit(1).Scan(ctx) + if err != nil { + return false + } + return true } From 24a98eb416737cc8832ff63f36afcab45f31dae7 Mon Sep 17 00:00:00 2001 From: zjy Date: Mon, 4 Dec 2023 17:50:47 +0800 Subject: [PATCH 033/210] feat: add user profile --- api/jiaozifs.gen.go | 218 ++++++++++++++++++++++++++++++++++------- api/swagger.yml | 20 ++++ auth/basic_auth.go | 55 ++++++++++- auth/jwt_login.go | 12 +-- controller/user_ctl.go | 15 +++ models/user.go | 6 ++ 6 files changed, 279 insertions(+), 47 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 65344397..cde0ac77 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -15,6 +15,7 @@ import ( "net/url" "path" "strings" + "time" "github.com/getkin/kin-openapi/openapi3" "github.com/go-chi/chi/v5" @@ -81,6 +82,18 @@ type RegistrationMsg struct { Message string `json:"message"` } +// UserInfo defines model for UserInfo. +type UserInfo struct { + CreatedAt *time.Time `json:"createdAt,omitempty"` + CurrentSignInAt *time.Time `json:"currentSignInAt,omitempty"` + CurrentSignInIP *string `json:"currentSignInIP,omitempty"` + Email openapi_types.Email `json:"email"` + LastSignInAt *time.Time `json:"lastSignInAt,omitempty"` + LastSignInIP *string `json:"lastSignInIP,omitempty"` + UpdateAt *time.Time `json:"updateAt,omitempty"` + Username string `json:"username"` +} + // UserRegisterInfo defines model for UserRegisterInfo. type UserRegisterInfo struct { Email openapi_types.Email `json:"email"` @@ -240,6 +253,9 @@ type ClientInterface interface { Register(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetUserInfo request + GetUserInfo(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // DeleteObject request DeleteObject(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -304,6 +320,18 @@ func (c *Client) Register(ctx context.Context, body RegisterJSONRequestBody, req return c.Client.Do(req) } +func (c *Client) GetUserInfo(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetUserInfoRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) DeleteObject(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewDeleteObjectRequest(c.Server, repository, params) if err != nil { @@ -444,6 +472,33 @@ func NewRegisterRequestWithBody(server string, contentType string, body io.Reade return req, nil } +// NewGetUserInfoRequest generates requests for GetUserInfo +func NewGetUserInfoRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/auth/user") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewDeleteObjectRequest generates requests for DeleteObject func NewDeleteObjectRequest(server string, repository string, params *DeleteObjectParams) (*http.Request, error) { var err error @@ -811,6 +866,9 @@ type ClientWithResponsesInterface interface { RegisterWithResponse(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterResponse, error) + // GetUserInfoWithResponse request + GetUserInfoWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetUserInfoResponse, error) + // DeleteObjectWithResponse request DeleteObjectWithResponse(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) @@ -871,6 +929,28 @@ func (r RegisterResponse) StatusCode() int { return 0 } +type GetUserInfoResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *UserInfo +} + +// Status returns HTTPResponse.Status +func (r GetUserInfoResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetUserInfoResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type DeleteObjectResponse struct { Body []byte HTTPResponse *http.Response @@ -1012,6 +1092,15 @@ func (c *ClientWithResponses) RegisterWithResponse(ctx context.Context, body Reg return ParseRegisterResponse(rsp) } +// GetUserInfoWithResponse request returning *GetUserInfoResponse +func (c *ClientWithResponses) GetUserInfoWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetUserInfoResponse, error) { + rsp, err := c.GetUserInfo(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetUserInfoResponse(rsp) +} + // DeleteObjectWithResponse request returning *DeleteObjectResponse func (c *ClientWithResponses) DeleteObjectWithResponse(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) { rsp, err := c.DeleteObject(ctx, repository, params, reqEditors...) @@ -1109,6 +1198,32 @@ func ParseRegisterResponse(rsp *http.Response) (*RegisterResponse, error) { return response, nil } +// ParseGetUserInfoResponse parses an HTTP response from a GetUserInfoWithResponse call +func ParseGetUserInfoResponse(rsp *http.Response) (*GetUserInfoResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetUserInfoResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest UserInfo + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseDeleteObjectResponse parses an HTTP response from a DeleteObjectWithResponse call func ParseDeleteObjectResponse(rsp *http.Response) (*DeleteObjectResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -1217,6 +1332,9 @@ type ServerInterface interface { // perform user registration // (POST /auth/register) Register(w *JiaozifsResponse, r *http.Request) + // get information of the currently logged-in user + // (GET /auth/user) + GetUserInfo(w *JiaozifsResponse, r *http.Request) // delete object. Missing objects will not return a NotFound error. // (DELETE /repositories/{repository}/objects) DeleteObject(w *JiaozifsResponse, r *http.Request, repository string, params DeleteObjectParams) @@ -1250,6 +1368,12 @@ func (_ Unimplemented) Register(w *JiaozifsResponse, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } +// get information of the currently logged-in user +// (GET /auth/user) +func (_ Unimplemented) GetUserInfo(w *JiaozifsResponse, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + // delete object. Missing objects will not return a NotFound error. // (DELETE /repositories/{repository}/objects) func (_ Unimplemented) DeleteObject(w *JiaozifsResponse, r *http.Request, repository string, params DeleteObjectParams) { @@ -1318,6 +1442,23 @@ func (siw *ServerInterfaceWrapper) Register(w http.ResponseWriter, r *http.Reque handler.ServeHTTP(w, r.WithContext(ctx)) } +// GetUserInfo operation middleware +func (siw *ServerInterfaceWrapper) GetUserInfo(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{"aaaa"}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetUserInfo(&JiaozifsResponse{w}, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // DeleteObject operation middleware func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -1725,6 +1866,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/auth/register", wrapper.Register) }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/auth/user", wrapper.GetUserInfo) + }) r.Group(func(r chi.Router) { r.Delete(options.BaseURL+"/repositories/{repository}/objects", wrapper.DeleteObject) }) @@ -1747,42 +1891,44 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 05bb2740..42380df8 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -526,3 +526,23 @@ paths: description: too many requests default: description: Internal Server Error + + /auth/user: + get: + tags: + - auth + operationId: getUserInfo + summary: get information of the currently logged-in user + security: + - jwt_token: ["aaaa"] + responses: + 200: + description: Successful get of user Info + content: + application/json: + schema: + $ref: "#/components/schemas/UserInfo" + 401: + description: Unauthorized + default: + description: Internal Server Error diff --git a/auth/basic_auth.go b/auth/basic_auth.go index 99f77aad..ffa37ea2 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -2,6 +2,9 @@ package auth import ( "context" + "errors" + "github.com/golang-jwt/jwt" + openapi_types "github.com/oapi-codegen/runtime/types" "time" "github.com/go-openapi/swag" @@ -23,6 +26,9 @@ type Register struct { Email string `json:"email"` Password string `json:"password"` } +type UserInfo struct { + Token string `json:"token"` +} func (l *Login) Login(userRepo models.IUserRepo, config *config.Config) (token api.AuthenticationToken, err error) { ctx := context.Background() @@ -56,15 +62,15 @@ func (l *Login) Login(userRepo models.IUserRepo, config *config.Config) (token a return token, nil } -func (l *Register) Register(userRepo models.IUserRepo) (msg api.RegistrationMsg, err error) { +func (r *Register) Register(userRepo models.IUserRepo) (msg api.RegistrationMsg, err error) { ctx := context.Background() // check username, email - if userRepo.CheckUserByNameEmail(ctx, l.Username, l.Email) { + if userRepo.CheckUserByNameEmail(ctx, r.Username, r.Email) { msg.Message = "The username or email has already been registered" return } - password, err := bcrypt.GenerateFromPassword([]byte(l.Password), passwordCost) + password, err := bcrypt.GenerateFromPassword([]byte(r.Password), passwordCost) if err != nil { msg.Message = "Generate Password err" return @@ -72,8 +78,8 @@ func (l *Register) Register(userRepo models.IUserRepo) (msg api.RegistrationMsg, // insert db user := &models.User{ - Name: l.Username, - Email: l.Email, + Name: r.Username, + Email: r.Email, EncryptedPassword: string(password), CurrentSignInAt: time.Time{}, LastSignInAt: time.Time{}, @@ -91,3 +97,42 @@ func (l *Register) Register(userRepo models.IUserRepo) (msg api.RegistrationMsg, msg.Message = insertUser.Name + " register success" return msg, nil } + +func (u *UserInfo) UserProfile(userRepo models.IUserRepo, config *config.Config) (api.UserInfo, error) { + ctx := context.Background() + userInfo := api.UserInfo{} + // Parse JWT Token + token, err := jwt.Parse(u.Token, func(token *jwt.Token) (interface{}, error) { + return config.Auth.SecretKey, nil + }) + if err != nil { + return userInfo, err + } + // Check Token validity + if !token.Valid { + return userInfo, errors.New("token is invalid") + } + // Get username by token + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + return userInfo, errors.New("failed to extract claims from JWT token") + } + username := claims["sub"].(string) + + // Get user by username + user, err := userRepo.GetUserByName(ctx, username) + if err != nil { + return userInfo, err + } + userInfo = api.UserInfo{ + CreatedAt: &user.CreatedAt, + CurrentSignInAt: &user.CurrentSignInAt, + CurrentSignInIP: &user.CurrentSignInIP, + Email: openapi_types.Email(user.Email), + LastSignInAt: &user.LastSignInAt, + LastSignInIP: &user.LastSignInIP, + UpdateAt: &user.UpdatedAt, + Username: user.Name, + } + return userInfo, nil +} diff --git a/auth/jwt_login.go b/auth/jwt_login.go index e1373f6a..0dbd533f 100644 --- a/auth/jwt_login.go +++ b/auth/jwt_login.go @@ -15,12 +15,12 @@ const ( // It supports backward compatibility for creating a login jwt. The audience is not set for login token. Any audience will make the token // invalid for login. No email is passed to support the ability of login for users via user/access keys which don't have an email yet func GenerateJWTLogin(secret []byte, userID string, issuedAt, expiresAt time.Time) (string, error) { - claims := &jwt.StandardClaims{ - Id: uuid.NewString(), - Audience: LoginAudience, - Subject: userID, - IssuedAt: issuedAt.Unix(), - ExpiresAt: expiresAt.Unix(), + claims := jwt.MapClaims{ + "id": uuid.NewString(), + "aud": LoginAudience, + "sub": userID, + "iat": issuedAt.Unix(), + "exp": expiresAt.Unix(), } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(secret) diff --git a/controller/user_ctl.go b/controller/user_ctl.go index 02be39b3..87fb7c39 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -54,3 +54,18 @@ func (A UserController) Register(w *api.JiaozifsResponse, r *http.Request) { // resp w.RespJSON(msg) } + +func (A UserController) GetUserInfo(w *api.JiaozifsResponse, r *http.Request) { + // Get token from Header + tokenString := r.Header.Get("Authorization") + userInfo := &auth.UserInfo{Token: tokenString} + + // Perform GetUserInfo + info, err := userInfo.UserProfile(*A.UserRepo, A.Config) + if err != nil { + w.RespError(err) + return + } + // resp + w.RespJSON(info) +} diff --git a/models/user.go b/models/user.go index 2b9c87ec..8d640f46 100644 --- a/models/user.go +++ b/models/user.go @@ -28,6 +28,7 @@ type IUserRepo interface { Insert(ctx context.Context, user *User) (*User, error) GetEPByName(ctx context.Context, name string) (string, error) + GetUserByName(ctx context.Context, name string) (*User, error) CheckUserByNameEmail(ctx context.Context, name, email string) bool } @@ -70,3 +71,8 @@ func (userRepo *UserRepo) CheckUserByNameEmail(ctx context.Context, name, email } return true } + +func (userRepo *UserRepo) GetUserByName(ctx context.Context, name string) (*User, error) { + user := &User{} + return user, userRepo.DB.NewSelect().Model(user).Where("name = ?", name).Scan(ctx) +} From ae162b053ff201f7aef233af7cd2f48a877e7c18 Mon Sep 17 00:00:00 2001 From: zjy Date: Mon, 4 Dec 2023 18:17:10 +0800 Subject: [PATCH 034/210] chore: add test, reformat code --- auth/basic_auth.go | 3 ++- models/user.go | 5 +---- models/user_test.go | 6 ++++++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/auth/basic_auth.go b/auth/basic_auth.go index ffa37ea2..729b0acd 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -3,9 +3,10 @@ package auth import ( "context" "errors" + "time" + "github.com/golang-jwt/jwt" openapi_types "github.com/oapi-codegen/runtime/types" - "time" "github.com/go-openapi/swag" logging "github.com/ipfs/go-log/v2" diff --git a/models/user.go b/models/user.go index 8d640f46..531fa7ef 100644 --- a/models/user.go +++ b/models/user.go @@ -66,10 +66,7 @@ func (userRepo *UserRepo) GetEPByName(ctx context.Context, name string) (string, func (userRepo *UserRepo) CheckUserByNameEmail(ctx context.Context, name, email string) bool { err := userRepo.DB.NewSelect().Model((*User)(nil)).Where("name = ? OR email = ?", name, email). Limit(1).Scan(ctx) - if err != nil { - return false - } - return true + return err != nil } func (userRepo *UserRepo) GetUserByName(ctx context.Context, name string) (*User, error) { diff --git a/models/user_test.go b/models/user_test.go index c0a6abd9..e6c5aa16 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -67,4 +67,10 @@ func TestNewUserRepo(t *testing.T) { ep, err := repo.GetEPByName(ctx, newUser.Name) require.NoError(t, err) require.True(t, cmp.Equal(userModel.EncryptedPassword, ep)) + + b := repo.CheckUserByNameEmail(ctx, newUser.Name, newUser.Email) + require.True(t, !b) + + user, err = repo.GetUserByName(ctx, newUser.Name) + require.NoError(t, err) } From 61b356b7d3307682d89f0548f4f8cfd2005fed31 Mon Sep 17 00:00:00 2001 From: zjy Date: Mon, 4 Dec 2023 18:24:17 +0800 Subject: [PATCH 035/210] fix: fix judge conditions --- models/user_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/models/user_test.go b/models/user_test.go index e6c5aa16..e4388c15 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -69,8 +69,9 @@ func TestNewUserRepo(t *testing.T) { require.True(t, cmp.Equal(userModel.EncryptedPassword, ep)) b := repo.CheckUserByNameEmail(ctx, newUser.Name, newUser.Email) - require.True(t, !b) + require.True(t, b) - user, err = repo.GetUserByName(ctx, newUser.Name) + u, err := repo.GetUserByName(ctx, newUser.Name) require.NoError(t, err) + require.True(t, cmp.Equal(userModel.UpdatedAt, u.UpdatedAt, dbTimeCmpOpt)) } From 20e7a924f21ab884cab291a5c7da2a7f935487ff Mon Sep 17 00:00:00 2001 From: zjy Date: Tue, 5 Dec 2023 15:54:05 +0800 Subject: [PATCH 036/210] refactor: add service interface based on the original code. --- auth/basic_auth.go | 40 ++++++++++++++--------------- auth/service.go | 57 ++++++++++++++++++++++++++++++++++++++++++ cmd/daemon.go | 3 +++ controller/user_ctl.go | 14 +++++------ models/user.go | 2 +- models/user_test.go | 2 +- 6 files changed, 88 insertions(+), 30 deletions(-) create mode 100644 auth/service.go diff --git a/auth/basic_auth.go b/auth/basic_auth.go index 729b0acd..64487b05 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -11,7 +11,6 @@ import ( "github.com/go-openapi/swag" logging "github.com/ipfs/go-log/v2" "github.com/jiaozifs/jiaozifs/api" - "github.com/jiaozifs/jiaozifs/config" "github.com/jiaozifs/jiaozifs/models" "golang.org/x/crypto/bcrypt" ) @@ -22,19 +21,10 @@ type Login struct { Username string `json:"username"` Password string `json:"password"` } -type Register struct { - Username string `json:"username"` - Email string `json:"email"` - Password string `json:"password"` -} -type UserInfo struct { - Token string `json:"token"` -} -func (l *Login) Login(userRepo models.IUserRepo, config *config.Config) (token api.AuthenticationToken, err error) { - ctx := context.Background() +func (l *Login) Login(ctx context.Context, authService Service) (token api.AuthenticationToken, err error) { // Get user encryptedPassword by username - ep, err := userRepo.GetEPByName(ctx, l.Username) + ep, err := authService.GetEPByName(ctx, l.Username) if err != nil { log.Errorf("username err: %s", err) return token, err @@ -49,7 +39,7 @@ func (l *Login) Login(userRepo models.IUserRepo, config *config.Config) (token a // Generate user token loginTime := time.Now() expires := loginTime.Add(expirationDuration) - secretKey := config.Auth.SecretKey + secretKey := authService.GetSecretKey() tokenString, err := GenerateJWTLogin(secretKey, l.Username, loginTime, expires) if err != nil { @@ -63,10 +53,15 @@ func (l *Login) Login(userRepo models.IUserRepo, config *config.Config) (token a return token, nil } -func (r *Register) Register(userRepo models.IUserRepo) (msg api.RegistrationMsg, err error) { - ctx := context.Background() +type Register struct { + Username string `json:"username"` + Email string `json:"email"` + Password string `json:"password"` +} + +func (r *Register) Register(ctx context.Context, authService Service) (msg api.RegistrationMsg, err error) { // check username, email - if userRepo.CheckUserByNameEmail(ctx, r.Username, r.Email) { + if authService.CheckUserByNameEmail(ctx, r.Username, r.Email) { msg.Message = "The username or email has already been registered" return } @@ -89,7 +84,7 @@ func (r *Register) Register(userRepo models.IUserRepo) (msg api.RegistrationMsg, CreatedAt: time.Now(), UpdatedAt: time.Time{}, } - insertUser, err := userRepo.Insert(ctx, user) + insertUser, err := authService.Insert(ctx, user) if err != nil { msg.Message = "register user err" return @@ -99,12 +94,15 @@ func (r *Register) Register(userRepo models.IUserRepo) (msg api.RegistrationMsg, return msg, nil } -func (u *UserInfo) UserProfile(userRepo models.IUserRepo, config *config.Config) (api.UserInfo, error) { - ctx := context.Background() +type UserInfo struct { + Token string `json:"token"` +} + +func (u *UserInfo) UserProfile(ctx context.Context, authService Service) (api.UserInfo, error) { userInfo := api.UserInfo{} // Parse JWT Token token, err := jwt.Parse(u.Token, func(token *jwt.Token) (interface{}, error) { - return config.Auth.SecretKey, nil + return authService.GetSecretKey(), nil }) if err != nil { return userInfo, err @@ -121,7 +119,7 @@ func (u *UserInfo) UserProfile(userRepo models.IUserRepo, config *config.Config) username := claims["sub"].(string) // Get user by username - user, err := userRepo.GetUserByName(ctx, username) + user, err := authService.GetUserByName(ctx, username) if err != nil { return userInfo, err } diff --git a/auth/service.go b/auth/service.go new file mode 100644 index 00000000..757d062e --- /dev/null +++ b/auth/service.go @@ -0,0 +1,57 @@ +package auth + +import ( + "context" + "github.com/jiaozifs/jiaozifs/config" + "github.com/jiaozifs/jiaozifs/models" +) + +type Service interface { + // user + Insert(ctx context.Context, user *models.User) (*models.User, error) + GetEPByName(ctx context.Context, name string) (string, error) + GetUserByName(ctx context.Context, name string) (*models.User, error) + CheckUserByNameEmail(ctx context.Context, name, email string) bool + // config + GetSecretKey() []byte +} + +var _ Service = (*AuthService)(nil) + +type AuthService struct { + UserRepo *models.IUserRepo + Config *config.Config +} + +func (a AuthService) GetUserByName(ctx context.Context, name string) (*models.User, error) { + return (*a.UserRepo).GetUserByName(ctx, name) +} + +func (a AuthService) Insert(ctx context.Context, user *models.User) (*models.User, error) { + return (*a.UserRepo).Insert(ctx, user) +} + +func (a AuthService) CheckUserByNameEmail(ctx context.Context, name, email string) bool { + return (*a.UserRepo).CheckUserByNameEmail(ctx, name, email) +} + +func (a AuthService) GetSecretKey() []byte { + return (*a.Config).Auth.SecretKey +} + +func (a AuthService) GetEPByName(ctx context.Context, name string) (string, error) { + ep, err := (*a.UserRepo).GetEPByName(ctx, name) + if err != nil { + return "", err + } + return ep, nil +} + +func NewAuthService(userRepo *models.IUserRepo, config *config.Config) *AuthService { + log.Info("initialized Auth service") + res := &AuthService{ + UserRepo: userRepo, + Config: config, + } + return res +} diff --git a/cmd/daemon.go b/cmd/daemon.go index cd5e8038..cbbcd83a 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -2,6 +2,7 @@ package cmd import ( "context" + "github.com/jiaozifs/jiaozifs/auth" "github.com/jiaozifs/jiaozifs/block/params" @@ -47,6 +48,8 @@ var daemonCmd = &cobra.Command{ fx_opt.Override(new(*config.APIConfig), &cfg.API), fx_opt.Override(new(*config.DatabaseConfig), &cfg.Database), fx_opt.Override(new(params.AdapterConfig), &cfg.Blockstore), + //auth + fx_opt.Override(new(auth.Service), auth.NewAuthService), //blockstore fx_opt.Override(new(block.Adapter), factory.BuildBlockAdapter), //database diff --git a/controller/user_ctl.go b/controller/user_ctl.go index 87fb7c39..a9964f74 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -6,19 +6,17 @@ import ( "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/auth" - "github.com/jiaozifs/jiaozifs/config" - "github.com/jiaozifs/jiaozifs/models" "go.uber.org/fx" ) type UserController struct { fx.In - UserRepo *models.IUserRepo - Config *config.Config + Auth auth.Service } func (A UserController) Login(w *api.JiaozifsResponse, r *http.Request) { + ctx := r.Context() // Decode requestBody var login auth.Login decoder := json.NewDecoder(r.Body) @@ -28,7 +26,7 @@ func (A UserController) Login(w *api.JiaozifsResponse, r *http.Request) { } // Perform login - resp, err := login.Login(*A.UserRepo, A.Config) + resp, err := login.Login(ctx, A.Auth) if err != nil { w.RespError(err) return @@ -39,6 +37,7 @@ func (A UserController) Login(w *api.JiaozifsResponse, r *http.Request) { } func (A UserController) Register(w *api.JiaozifsResponse, r *http.Request) { + ctx := r.Context() // Decode requestBody var register auth.Register decoder := json.NewDecoder(r.Body) @@ -46,7 +45,7 @@ func (A UserController) Register(w *api.JiaozifsResponse, r *http.Request) { w.RespError(err) } // Perform register - msg, err := register.Register(*A.UserRepo) + msg, err := register.Register(ctx, A.Auth) if err != nil { w.RespError(err) return @@ -56,12 +55,13 @@ func (A UserController) Register(w *api.JiaozifsResponse, r *http.Request) { } func (A UserController) GetUserInfo(w *api.JiaozifsResponse, r *http.Request) { + ctx := r.Context() // Get token from Header tokenString := r.Header.Get("Authorization") userInfo := &auth.UserInfo{Token: tokenString} // Perform GetUserInfo - info, err := userInfo.UserProfile(*A.UserRepo, A.Config) + info, err := userInfo.UserProfile(ctx, A.Auth) if err != nil { w.RespError(err) return diff --git a/models/user.go b/models/user.go index 531fa7ef..87c2bd3e 100644 --- a/models/user.go +++ b/models/user.go @@ -66,7 +66,7 @@ func (userRepo *UserRepo) GetEPByName(ctx context.Context, name string) (string, func (userRepo *UserRepo) CheckUserByNameEmail(ctx context.Context, name, email string) bool { err := userRepo.DB.NewSelect().Model((*User)(nil)).Where("name = ? OR email = ?", name, email). Limit(1).Scan(ctx) - return err != nil + return err == nil } func (userRepo *UserRepo) GetUserByName(ctx context.Context, name string) (*User, error) { diff --git a/models/user_test.go b/models/user_test.go index e4388c15..780ef9a6 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -69,7 +69,7 @@ func TestNewUserRepo(t *testing.T) { require.True(t, cmp.Equal(userModel.EncryptedPassword, ep)) b := repo.CheckUserByNameEmail(ctx, newUser.Name, newUser.Email) - require.True(t, b) + require.True(t, !b) u, err := repo.GetUserByName(ctx, newUser.Name) require.NoError(t, err) From a3bb3b6800b9e5d4edec587a43e13948b051238d Mon Sep 17 00:00:00 2001 From: zjy Date: Tue, 5 Dec 2023 16:06:53 +0800 Subject: [PATCH 037/210] chore: goimports code --- auth/service.go | 1 + cmd/daemon.go | 1 + 2 files changed, 2 insertions(+) diff --git a/auth/service.go b/auth/service.go index 757d062e..80c9b71b 100644 --- a/auth/service.go +++ b/auth/service.go @@ -2,6 +2,7 @@ package auth import ( "context" + "github.com/jiaozifs/jiaozifs/config" "github.com/jiaozifs/jiaozifs/models" ) diff --git a/cmd/daemon.go b/cmd/daemon.go index cbbcd83a..bd32bd99 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -2,6 +2,7 @@ package cmd import ( "context" + "github.com/jiaozifs/jiaozifs/auth" "github.com/jiaozifs/jiaozifs/block/params" From 433492f7787010f574165ba8eefe494a15aebd67 Mon Sep 17 00:00:00 2001 From: zjy Date: Tue, 5 Dec 2023 16:22:38 +0800 Subject: [PATCH 038/210] chore: update Service impl name --- auth/service.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/auth/service.go b/auth/service.go index 80c9b71b..d06e4b31 100644 --- a/auth/service.go +++ b/auth/service.go @@ -17,30 +17,30 @@ type Service interface { GetSecretKey() []byte } -var _ Service = (*AuthService)(nil) +var _ Service = (*ServiceAuth)(nil) -type AuthService struct { +type ServiceAuth struct { UserRepo *models.IUserRepo Config *config.Config } -func (a AuthService) GetUserByName(ctx context.Context, name string) (*models.User, error) { +func (a ServiceAuth) GetUserByName(ctx context.Context, name string) (*models.User, error) { return (*a.UserRepo).GetUserByName(ctx, name) } -func (a AuthService) Insert(ctx context.Context, user *models.User) (*models.User, error) { +func (a ServiceAuth) Insert(ctx context.Context, user *models.User) (*models.User, error) { return (*a.UserRepo).Insert(ctx, user) } -func (a AuthService) CheckUserByNameEmail(ctx context.Context, name, email string) bool { +func (a ServiceAuth) CheckUserByNameEmail(ctx context.Context, name, email string) bool { return (*a.UserRepo).CheckUserByNameEmail(ctx, name, email) } -func (a AuthService) GetSecretKey() []byte { +func (a ServiceAuth) GetSecretKey() []byte { return (*a.Config).Auth.SecretKey } -func (a AuthService) GetEPByName(ctx context.Context, name string) (string, error) { +func (a ServiceAuth) GetEPByName(ctx context.Context, name string) (string, error) { ep, err := (*a.UserRepo).GetEPByName(ctx, name) if err != nil { return "", err @@ -48,9 +48,9 @@ func (a AuthService) GetEPByName(ctx context.Context, name string) (string, erro return ep, nil } -func NewAuthService(userRepo *models.IUserRepo, config *config.Config) *AuthService { +func NewAuthService(userRepo *models.IUserRepo, config *config.Config) *ServiceAuth { log.Info("initialized Auth service") - res := &AuthService{ + res := &ServiceAuth{ UserRepo: userRepo, Config: config, } From 63e606948738749dd55bfa66ba0491d200c25852 Mon Sep 17 00:00:00 2001 From: zjy Date: Tue, 5 Dec 2023 16:49:09 +0800 Subject: [PATCH 039/210] feat: add some log info --- auth/basic_auth.go | 19 ++++++++++++++----- auth/errors.go | 11 +++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 auth/errors.go diff --git a/auth/basic_auth.go b/auth/basic_auth.go index 64487b05..1dcedb64 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -2,7 +2,6 @@ package auth import ( "context" - "errors" "time" "github.com/golang-jwt/jwt" @@ -47,6 +46,8 @@ func (l *Login) Login(ctx context.Context, authService Service) (token api.Authe return token, err } + log.Info("login successful") + token.Token = tokenString token.TokenExpiration = swag.Int64(expires.Unix()) @@ -61,14 +62,16 @@ type Register struct { func (r *Register) Register(ctx context.Context, authService Service) (msg api.RegistrationMsg, err error) { // check username, email - if authService.CheckUserByNameEmail(ctx, r.Username, r.Email) { + if !authService.CheckUserByNameEmail(ctx, r.Username, r.Email) { msg.Message = "The username or email has already been registered" + log.Error(ErrInvalidNameEmail) return } password, err := bcrypt.GenerateFromPassword([]byte(r.Password), passwordCost) if err != nil { - msg.Message = "Generate Password err" + msg.Message = "compare password err" + log.Error(ErrComparePassword) return } @@ -87,9 +90,11 @@ func (r *Register) Register(ctx context.Context, authService Service) (msg api.R insertUser, err := authService.Insert(ctx, user) if err != nil { msg.Message = "register user err" + log.Error(msg.Message) return } // return + log.Info("registration success") msg.Message = insertUser.Name + " register success" return msg, nil } @@ -105,16 +110,19 @@ func (u *UserInfo) UserProfile(ctx context.Context, authService Service) (api.Us return authService.GetSecretKey(), nil }) if err != nil { + log.Error(ErrParseToken) return userInfo, err } // Check Token validity if !token.Valid { - return userInfo, errors.New("token is invalid") + log.Error(ErrInvalidToken) + return userInfo, ErrInvalidToken } // Get username by token claims, ok := token.Claims.(jwt.MapClaims) if !ok { - return userInfo, errors.New("failed to extract claims from JWT token") + log.Error(ErrExtractClaims) + return userInfo, ErrExtractClaims } username := claims["sub"].(string) @@ -133,5 +141,6 @@ func (u *UserInfo) UserProfile(ctx context.Context, authService Service) (api.Us UpdateAt: &user.UpdatedAt, Username: user.Name, } + log.Info("get user profile success") return userInfo, nil } diff --git a/auth/errors.go b/auth/errors.go new file mode 100644 index 00000000..3e824527 --- /dev/null +++ b/auth/errors.go @@ -0,0 +1,11 @@ +package auth + +import "errors" + +var ( + ErrComparePassword = errors.New("compare password error") + ErrParseToken = errors.New("parse token error") + ErrInvalidToken = errors.New("invalid token") + ErrInvalidNameEmail = errors.New("invalid name or email") + ErrExtractClaims = errors.New("failed to extract claims from JWT token") +) From fe9da01f123ddf4d840d27575efbb51ae0622d78 Mon Sep 17 00:00:00 2001 From: zjy Date: Wed, 6 Dec 2023 20:48:05 +0800 Subject: [PATCH 040/210] fix: remove redundant parameters and update rep code --- api/swagger.yml | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/api/swagger.yml b/api/swagger.yml index 42380df8..5fab774d 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -13,8 +13,8 @@ servers: description: jiaozifs server endpoint security: - - jwt_token: ["aaaa"] - - basic_auth: ["aaaaa"] + - jwt_token: [] + - basic_auth: [] components: securitySchemes: basic_auth: @@ -82,14 +82,6 @@ components: type: string format: date-time - RegistrationMsg: - type: object - required: - - message - properties: - message: - type: string - UserRegisterInfo: type: object required: @@ -515,11 +507,7 @@ paths: $ref: "#/components/schemas/UserRegisterInfo" responses: 201: - description: registration success message - content: - application/json: - schema: - $ref: "#/components/schemas/RegistrationMsg" + description: registration success 400: description: Bad Request - Validation Error 420: @@ -534,7 +522,7 @@ paths: operationId: getUserInfo summary: get information of the currently logged-in user security: - - jwt_token: ["aaaa"] + - jwt_token: [] responses: 200: description: Successful get of user Info From 24fdd643078eef4bc64a2805c0d8c135a054f3f3 Mon Sep 17 00:00:00 2001 From: zjy Date: Wed, 6 Dec 2023 20:49:10 +0800 Subject: [PATCH 041/210] test: add auth test --- auth/auth_test.go | 70 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 auth/auth_test.go diff --git a/auth/auth_test.go b/auth/auth_test.go new file mode 100644 index 00000000..8ee1aad3 --- /dev/null +++ b/auth/auth_test.go @@ -0,0 +1,70 @@ +package auth + +import ( + "context" + "fmt" + "github.com/brianvoe/gofakeit/v6" + embeddedpostgres "github.com/fergusstrange/embedded-postgres" + "github.com/jiaozifs/jiaozifs/config" + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/models/migrations" + "github.com/phayes/freeport" + "github.com/stretchr/testify/require" + "github.com/uptrace/bun" + "go.uber.org/fx/fxtest" + "testing" +) + +var testConnTmpl = "postgres://postgres:postgres@localhost:%d/jiaozifs?sslmode=disable" + +func setup(ctx context.Context, t *testing.T) (*embeddedpostgres.EmbeddedPostgres, *bun.DB) { + port, err := freeport.GetFreePort() + require.NoError(t, err) + postgres := embeddedpostgres.NewDatabase(embeddedpostgres.DefaultConfig().Port(uint32(port)).Database("jiaozifs")) + err = postgres.Start() + require.NoError(t, err) + + db, err := models.SetupDatabase(ctx, fxtest.NewLifecycle(t), &config.DatabaseConfig{Debug: true, Connection: fmt.Sprintf(testConnTmpl, port)}) + require.NoError(t, err) + + err = migrations.MigrateDatabase(ctx, db) + require.NoError(t, err) + return postgres, db +} + +func TestLogin_Success(t *testing.T) { + ctx := context.Background() + postgres, db := setup(ctx, t) + defer postgres.Stop() //nolint + // repo + mockRepo := models.NewUserRepo(db) + // config + mockConfig := &config.Config{Auth: config.AuthConfig{SecretKey: []byte("THIS_MUST_BE_CHANGED_IN_PRODUCTION")}} + // user + userModel := &models.User{} + require.NoError(t, gofakeit.Struct(userModel)) + + // registration + register := &Register{ + Username: userModel.Name, + Email: userModel.Email, + Password: userModel.EncryptedPassword, + } + err := register.Register(ctx, mockRepo) + require.NoError(t, err) + + // login + login := &Login{ + Username: userModel.Name, + Password: userModel.EncryptedPassword, + } + token, err := login.Login(context.Background(), mockRepo, mockConfig) + require.NoError(t, err, "Login should not return an error") + require.NotEmpty(t, token.Token, "Token should not be empty") + require.NotNil(t, token.TokenExpiration, "Token expiration should not be nil") + // profile + userInfo := &UserInfo{Token: token.Token} + profile, err := userInfo.UserProfile(ctx, mockRepo, mockConfig) + require.NoError(t, err) + require.NotEmpty(t, profile) +} From e7fd8f8f14627dd2c3a6714e634306272cb6892c Mon Sep 17 00:00:00 2001 From: zjy Date: Wed, 6 Dec 2023 20:55:16 +0800 Subject: [PATCH 042/210] refactor: remove auth interface --- auth/basic_auth.go | 32 +++++++++++------------ auth/service.go | 58 ------------------------------------------ controller/user_ctl.go | 13 ++++++---- 3 files changed, 24 insertions(+), 79 deletions(-) delete mode 100644 auth/service.go diff --git a/auth/basic_auth.go b/auth/basic_auth.go index 1dcedb64..8fb8a641 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -2,6 +2,7 @@ package auth import ( "context" + "github.com/jiaozifs/jiaozifs/config" "time" "github.com/golang-jwt/jwt" @@ -21,9 +22,9 @@ type Login struct { Password string `json:"password"` } -func (l *Login) Login(ctx context.Context, authService Service) (token api.AuthenticationToken, err error) { +func (l *Login) Login(ctx context.Context, repo models.IUserRepo, config *config.Config) (token api.AuthenticationToken, err error) { // Get user encryptedPassword by username - ep, err := authService.GetEPByName(ctx, l.Username) + ep, err := repo.GetEPByName(ctx, l.Username) if err != nil { log.Errorf("username err: %s", err) return token, err @@ -38,7 +39,7 @@ func (l *Login) Login(ctx context.Context, authService Service) (token api.Authe // Generate user token loginTime := time.Now() expires := loginTime.Add(expirationDuration) - secretKey := authService.GetSecretKey() + secretKey := config.Auth.SecretKey tokenString, err := GenerateJWTLogin(secretKey, l.Username, loginTime, expires) if err != nil { @@ -60,17 +61,18 @@ type Register struct { Password string `json:"password"` } -func (r *Register) Register(ctx context.Context, authService Service) (msg api.RegistrationMsg, err error) { +func (r *Register) Register(ctx context.Context, repo models.IUserRepo) (err error) { // check username, email - if !authService.CheckUserByNameEmail(ctx, r.Username, r.Email) { - msg.Message = "The username or email has already been registered" + _, err1 := repo.GetUserByName(ctx, r.Username) + _, err2 := repo.GetUserByEmail(ctx, r.Email) + if err1 == nil || err2 == nil { + err = ErrInvalidNameEmail log.Error(ErrInvalidNameEmail) return } password, err := bcrypt.GenerateFromPassword([]byte(r.Password), passwordCost) if err != nil { - msg.Message = "compare password err" log.Error(ErrComparePassword) return } @@ -87,27 +89,25 @@ func (r *Register) Register(ctx context.Context, authService Service) (msg api.R CreatedAt: time.Now(), UpdatedAt: time.Time{}, } - insertUser, err := authService.Insert(ctx, user) + insertUser, err := repo.Insert(ctx, user) if err != nil { - msg.Message = "register user err" - log.Error(msg.Message) + log.Error("create user error") return } // return - log.Info("registration success") - msg.Message = insertUser.Name + " register success" - return msg, nil + log.Infof("%s registration success", insertUser.Name) + return nil } type UserInfo struct { Token string `json:"token"` } -func (u *UserInfo) UserProfile(ctx context.Context, authService Service) (api.UserInfo, error) { +func (u *UserInfo) UserProfile(ctx context.Context, repo models.IUserRepo, config *config.Config) (api.UserInfo, error) { userInfo := api.UserInfo{} // Parse JWT Token token, err := jwt.Parse(u.Token, func(token *jwt.Token) (interface{}, error) { - return authService.GetSecretKey(), nil + return config.Auth.SecretKey, nil }) if err != nil { log.Error(ErrParseToken) @@ -127,7 +127,7 @@ func (u *UserInfo) UserProfile(ctx context.Context, authService Service) (api.Us username := claims["sub"].(string) // Get user by username - user, err := authService.GetUserByName(ctx, username) + user, err := repo.GetUserByName(ctx, username) if err != nil { return userInfo, err } diff --git a/auth/service.go b/auth/service.go deleted file mode 100644 index d06e4b31..00000000 --- a/auth/service.go +++ /dev/null @@ -1,58 +0,0 @@ -package auth - -import ( - "context" - - "github.com/jiaozifs/jiaozifs/config" - "github.com/jiaozifs/jiaozifs/models" -) - -type Service interface { - // user - Insert(ctx context.Context, user *models.User) (*models.User, error) - GetEPByName(ctx context.Context, name string) (string, error) - GetUserByName(ctx context.Context, name string) (*models.User, error) - CheckUserByNameEmail(ctx context.Context, name, email string) bool - // config - GetSecretKey() []byte -} - -var _ Service = (*ServiceAuth)(nil) - -type ServiceAuth struct { - UserRepo *models.IUserRepo - Config *config.Config -} - -func (a ServiceAuth) GetUserByName(ctx context.Context, name string) (*models.User, error) { - return (*a.UserRepo).GetUserByName(ctx, name) -} - -func (a ServiceAuth) Insert(ctx context.Context, user *models.User) (*models.User, error) { - return (*a.UserRepo).Insert(ctx, user) -} - -func (a ServiceAuth) CheckUserByNameEmail(ctx context.Context, name, email string) bool { - return (*a.UserRepo).CheckUserByNameEmail(ctx, name, email) -} - -func (a ServiceAuth) GetSecretKey() []byte { - return (*a.Config).Auth.SecretKey -} - -func (a ServiceAuth) GetEPByName(ctx context.Context, name string) (string, error) { - ep, err := (*a.UserRepo).GetEPByName(ctx, name) - if err != nil { - return "", err - } - return ep, nil -} - -func NewAuthService(userRepo *models.IUserRepo, config *config.Config) *ServiceAuth { - log.Info("initialized Auth service") - res := &ServiceAuth{ - UserRepo: userRepo, - Config: config, - } - return res -} diff --git a/controller/user_ctl.go b/controller/user_ctl.go index a9964f74..1b159db8 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -2,6 +2,8 @@ package controller import ( "encoding/json" + "github.com/jiaozifs/jiaozifs/config" + "github.com/jiaozifs/jiaozifs/models" "net/http" "github.com/jiaozifs/jiaozifs/api" @@ -12,7 +14,8 @@ import ( type UserController struct { fx.In - Auth auth.Service + Repo models.IUserRepo + Config *config.Config } func (A UserController) Login(w *api.JiaozifsResponse, r *http.Request) { @@ -26,7 +29,7 @@ func (A UserController) Login(w *api.JiaozifsResponse, r *http.Request) { } // Perform login - resp, err := login.Login(ctx, A.Auth) + resp, err := login.Login(ctx, A.Repo, A.Config) if err != nil { w.RespError(err) return @@ -45,13 +48,13 @@ func (A UserController) Register(w *api.JiaozifsResponse, r *http.Request) { w.RespError(err) } // Perform register - msg, err := register.Register(ctx, A.Auth) + err := register.Register(ctx, A.Repo) if err != nil { w.RespError(err) return } // resp - w.RespJSON(msg) + w.RespJSON("registration success") } func (A UserController) GetUserInfo(w *api.JiaozifsResponse, r *http.Request) { @@ -61,7 +64,7 @@ func (A UserController) GetUserInfo(w *api.JiaozifsResponse, r *http.Request) { userInfo := &auth.UserInfo{Token: tokenString} // Perform GetUserInfo - info, err := userInfo.UserProfile(ctx, A.Auth) + info, err := userInfo.UserProfile(ctx, A.Repo, A.Config) if err != nil { w.RespError(err) return From a79eab8d9d34a3d4899b7b3a15d8055cf0962846 Mon Sep 17 00:00:00 2001 From: zjy Date: Wed, 6 Dec 2023 20:57:59 +0800 Subject: [PATCH 043/210] chore: remove parameter references, remove resp msg, remove GetUserByNameEmail and add GetUserByEmail --- api/jiaozifs.gen.go | 110 +++++++++++++++++++------------------------- cmd/daemon.go | 12 ++--- models/user.go | 14 +++--- models/user_test.go | 9 ++-- 4 files changed, 63 insertions(+), 82 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index cde0ac77..43ac5ca8 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -77,11 +77,6 @@ type ObjectStatsPathType string // ObjectUserMetadata defines model for ObjectUserMetadata. type ObjectUserMetadata map[string]string -// RegistrationMsg defines model for RegistrationMsg. -type RegistrationMsg struct { - Message string `json:"message"` -} - // UserInfo defines model for UserInfo. type UserInfo struct { CreatedAt *time.Time `json:"createdAt,omitempty"` @@ -910,7 +905,6 @@ func (r LoginResponse) StatusCode() int { type RegisterResponse struct { Body []byte HTTPResponse *http.Response - JSON201 *RegistrationMsg } // Status returns HTTPResponse.Status @@ -1185,16 +1179,6 @@ func ParseRegisterResponse(rsp *http.Response) (*RegisterResponse, error) { HTTPResponse: rsp, } - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest RegistrationMsg - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON201 = &dest - - } - return response, nil } @@ -1446,7 +1430,7 @@ func (siw *ServerInterfaceWrapper) Register(w http.ResponseWriter, r *http.Reque func (siw *ServerInterfaceWrapper) GetUserInfo(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{"aaaa"}) + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetUserInfo(&JiaozifsResponse{w}, r) @@ -1474,9 +1458,9 @@ func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.R return } - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{"aaaa"}) + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - ctx = context.WithValue(ctx, Basic_authScopes, []string{"aaaaa"}) + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params DeleteObjectParams @@ -1522,9 +1506,9 @@ func (siw *ServerInterfaceWrapper) GetObject(w http.ResponseWriter, r *http.Requ return } - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{"aaaa"}) + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - ctx = context.WithValue(ctx, Basic_authScopes, []string{"aaaaa"}) + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params GetObjectParams @@ -1599,9 +1583,9 @@ func (siw *ServerInterfaceWrapper) HeadObject(w http.ResponseWriter, r *http.Req return } - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{"aaaa"}) + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - ctx = context.WithValue(ctx, Basic_authScopes, []string{"aaaaa"}) + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params HeadObjectParams @@ -1668,9 +1652,9 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R return } - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{"aaaa"}) + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - ctx = context.WithValue(ctx, Basic_authScopes, []string{"aaaaa"}) + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params UploadObjectParams @@ -1891,44 +1875,44 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "2WK0+HcAAAD//87c0PRRIwAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/cmd/daemon.go b/cmd/daemon.go index bd32bd99..425a0e4c 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -3,8 +3,6 @@ package cmd import ( "context" - "github.com/jiaozifs/jiaozifs/auth" - "github.com/jiaozifs/jiaozifs/block/params" "github.com/jiaozifs/jiaozifs/block" @@ -49,17 +47,15 @@ var daemonCmd = &cobra.Command{ fx_opt.Override(new(*config.APIConfig), &cfg.API), fx_opt.Override(new(*config.DatabaseConfig), &cfg.Database), fx_opt.Override(new(params.AdapterConfig), &cfg.Blockstore), - //auth - fx_opt.Override(new(auth.Service), auth.NewAuthService), //blockstore fx_opt.Override(new(block.Adapter), factory.BuildBlockAdapter), //database fx_opt.Override(new(*bun.DB), models.SetupDatabase), fx_opt.Override(fx_opt.NextInvoke(), migrations.MigrateDatabase), - fx_opt.Override(new(*models.IUserRepo), models.NewUserRepo), - fx_opt.Override(new(*models.IRepositoryRepo), models.NewRepositoryRepo), - fx_opt.Override(new(*models.IRefRepo), models.NewRefRepo), - fx_opt.Override(new(*models.IObjectRepo), models.NewObjectRepo), + fx_opt.Override(new(models.IUserRepo), models.NewUserRepo), + fx_opt.Override(new(models.IRepositoryRepo), models.NewRepositoryRepo), + fx_opt.Override(new(models.IRefRepo), models.NewRefRepo), + fx_opt.Override(new(models.IObjectRepo), models.NewObjectRepo), //api fx_opt.Override(fx_opt.NextInvoke(), apiImpl.SetupAPI), diff --git a/models/user.go b/models/user.go index 87c2bd3e..1104a7df 100644 --- a/models/user.go +++ b/models/user.go @@ -29,7 +29,7 @@ type IUserRepo interface { GetEPByName(ctx context.Context, name string) (string, error) GetUserByName(ctx context.Context, name string) (*User, error) - CheckUserByNameEmail(ctx context.Context, name, email string) bool + GetUserByEmail(ctx context.Context, email string) (*User, error) } var _ IUserRepo = (*UserRepo)(nil) @@ -63,13 +63,13 @@ func (userRepo *UserRepo) GetEPByName(ctx context.Context, name string) (string, Scan(ctx, &ep) } -func (userRepo *UserRepo) CheckUserByNameEmail(ctx context.Context, name, email string) bool { - err := userRepo.DB.NewSelect().Model((*User)(nil)).Where("name = ? OR email = ?", name, email). - Limit(1).Scan(ctx) - return err == nil -} - func (userRepo *UserRepo) GetUserByName(ctx context.Context, name string) (*User, error) { user := &User{} return user, userRepo.DB.NewSelect().Model(user).Where("name = ?", name).Scan(ctx) } + +func (userRepo *UserRepo) GetUserByEmail(ctx context.Context, email string) (*User, error) { + user := &User{} + return user, userRepo.DB.NewSelect(). + Model(user).Where("email = ?", email).Scan(ctx) +} diff --git a/models/user_test.go b/models/user_test.go index 780ef9a6..d0ffc743 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -68,10 +68,11 @@ func TestNewUserRepo(t *testing.T) { require.NoError(t, err) require.True(t, cmp.Equal(userModel.EncryptedPassword, ep)) - b := repo.CheckUserByNameEmail(ctx, newUser.Name, newUser.Email) - require.True(t, !b) + userByEmail, err := repo.GetUserByEmail(ctx, newUser.Email) + require.NoError(t, err) + require.True(t, cmp.Equal(userModel.UpdatedAt, userByEmail.UpdatedAt, dbTimeCmpOpt)) - u, err := repo.GetUserByName(ctx, newUser.Name) + userByName, err := repo.GetUserByName(ctx, newUser.Name) require.NoError(t, err) - require.True(t, cmp.Equal(userModel.UpdatedAt, u.UpdatedAt, dbTimeCmpOpt)) + require.True(t, cmp.Equal(userModel.UpdatedAt, userByName.UpdatedAt, dbTimeCmpOpt)) } From 1deb9e8600bf996365e30d93a3b26a1ef8387535 Mon Sep 17 00:00:00 2001 From: zjy Date: Wed, 6 Dec 2023 21:00:09 +0800 Subject: [PATCH 044/210] fix: remove hash password --- auth/basic_auth.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/auth/basic_auth.go b/auth/basic_auth.go index 8fb8a641..ed07fb78 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -71,17 +71,11 @@ func (r *Register) Register(ctx context.Context, repo models.IUserRepo) (err err return } - password, err := bcrypt.GenerateFromPassword([]byte(r.Password), passwordCost) - if err != nil { - log.Error(ErrComparePassword) - return - } - // insert db user := &models.User{ Name: r.Username, Email: r.Email, - EncryptedPassword: string(password), + EncryptedPassword: r.Password, CurrentSignInAt: time.Time{}, LastSignInAt: time.Time{}, CurrentSignInIP: "", From 69c43110c706dab152abf026712a41fecefa0873 Mon Sep 17 00:00:00 2001 From: zjy Date: Wed, 6 Dec 2023 21:01:45 +0800 Subject: [PATCH 045/210] Revert "fix: remove hash password" This reverts commit 1deb9e8600bf996365e30d93a3b26a1ef8387535. --- auth/basic_auth.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/auth/basic_auth.go b/auth/basic_auth.go index ed07fb78..8fb8a641 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -71,11 +71,17 @@ func (r *Register) Register(ctx context.Context, repo models.IUserRepo) (err err return } + password, err := bcrypt.GenerateFromPassword([]byte(r.Password), passwordCost) + if err != nil { + log.Error(ErrComparePassword) + return + } + // insert db user := &models.User{ Name: r.Username, Email: r.Email, - EncryptedPassword: r.Password, + EncryptedPassword: string(password), CurrentSignInAt: time.Time{}, LastSignInAt: time.Time{}, CurrentSignInIP: "", From 90de0593e21ed0944537efbea6eaab33ecb45598 Mon Sep 17 00:00:00 2001 From: zjy Date: Wed, 6 Dec 2023 21:04:35 +0800 Subject: [PATCH 046/210] docs: add comments for bcrypt.GenerateFromPassword --- auth/basic_auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth/basic_auth.go b/auth/basic_auth.go index 8fb8a641..98e6d160 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -70,7 +70,7 @@ func (r *Register) Register(ctx context.Context, repo models.IUserRepo) (err err log.Error(ErrInvalidNameEmail) return } - + // reserve temporarily password, err := bcrypt.GenerateFromPassword([]byte(r.Password), passwordCost) if err != nil { log.Error(ErrComparePassword) From d094a4502603d4b8f2779e654c53d1ba49fd913a Mon Sep 17 00:00:00 2001 From: zjy Date: Wed, 6 Dec 2023 21:05:24 +0800 Subject: [PATCH 047/210] style: goimports code --- auth/auth_test.go | 3 ++- auth/basic_auth.go | 3 ++- controller/user_ctl.go | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/auth/auth_test.go b/auth/auth_test.go index 8ee1aad3..63b0d708 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -3,6 +3,8 @@ package auth import ( "context" "fmt" + "testing" + "github.com/brianvoe/gofakeit/v6" embeddedpostgres "github.com/fergusstrange/embedded-postgres" "github.com/jiaozifs/jiaozifs/config" @@ -12,7 +14,6 @@ import ( "github.com/stretchr/testify/require" "github.com/uptrace/bun" "go.uber.org/fx/fxtest" - "testing" ) var testConnTmpl = "postgres://postgres:postgres@localhost:%d/jiaozifs?sslmode=disable" diff --git a/auth/basic_auth.go b/auth/basic_auth.go index 98e6d160..d69d3678 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -2,9 +2,10 @@ package auth import ( "context" - "github.com/jiaozifs/jiaozifs/config" "time" + "github.com/jiaozifs/jiaozifs/config" + "github.com/golang-jwt/jwt" openapi_types "github.com/oapi-codegen/runtime/types" diff --git a/controller/user_ctl.go b/controller/user_ctl.go index 1b159db8..221af5e6 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -2,9 +2,10 @@ package controller import ( "encoding/json" + "net/http" + "github.com/jiaozifs/jiaozifs/config" "github.com/jiaozifs/jiaozifs/models" - "net/http" "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/auth" From 471869cb8faca4d9555189fc5ea3772488da3006 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Sat, 2 Dec 2023 20:45:01 +0800 Subject: [PATCH 048/210] feat: add modify code --- api/custom_response.go | 13 +- api/swagger.yml | 19 +- api/tmpls/chi/chi-interface.tmpl | 4 +- api/tmpls/chi/chi-middleware.tmpl | 2 +- block/adapter.go | 1 - block/azure/adapter.go | 4 +- block/azure/multipart_block_writer.go | 6 +- block/local/adapter.go | 8 +- controller/coremgr.go | 155 +++ controller/object_ctl.go | 202 ++- controller/version_ctl.go | 7 +- models/object.go | 131 +- models/ref.go | 35 +- models/repository.go | 30 +- models/user.go | 12 +- models/wip.go | 79 ++ utils/convert_types.go | 918 ++++++++++++++ utils/convert_types_test.go | 1532 +++++++++++++++++++++++ utils/hash/hash.go | 55 +- utils/hash/hash_sha1.go | 15 - utils/hash/hash_sha256.go | 15 - utils/hash/hash_test.go | 103 -- {block => utils/hash}/hashing_reader.go | 70 +- utils/hash/hashing_reader_test.go | 65 + utils/httputil/client.go | 14 + utils/httputil/content_type.go | 16 + utils/httputil/content_type_test.go | 25 + utils/httputil/endpoints.go | 45 + utils/httputil/formats.go | 22 + utils/httputil/metrics.go | 17 + utils/httputil/range.go | 91 ++ utils/httputil/range_test.go | 60 + utils/httputil/request.go | 11 + utils/httputil/response.go | 8 + utils/httputil/scheme.go | 21 + utils/httputil/server.go | 53 + utils/httputil/server_test.go | 83 ++ utils/httputil/tracing.go | 105 ++ utils/pathutil/hash_path.go | 12 + utils/pathutil/hash_path_test.go | 15 + 40 files changed, 3813 insertions(+), 266 deletions(-) create mode 100644 controller/coremgr.go create mode 100644 models/wip.go create mode 100644 utils/convert_types.go create mode 100644 utils/convert_types_test.go delete mode 100644 utils/hash/hash_sha1.go delete mode 100644 utils/hash/hash_sha256.go delete mode 100644 utils/hash/hash_test.go rename {block => utils/hash}/hashing_reader.go (57%) create mode 100644 utils/hash/hashing_reader_test.go create mode 100644 utils/httputil/client.go create mode 100644 utils/httputil/content_type.go create mode 100644 utils/httputil/content_type_test.go create mode 100644 utils/httputil/endpoints.go create mode 100644 utils/httputil/formats.go create mode 100644 utils/httputil/metrics.go create mode 100644 utils/httputil/range.go create mode 100644 utils/httputil/range_test.go create mode 100644 utils/httputil/request.go create mode 100644 utils/httputil/response.go create mode 100644 utils/httputil/scheme.go create mode 100644 utils/httputil/server.go create mode 100644 utils/httputil/server_test.go create mode 100644 utils/httputil/tracing.go create mode 100644 utils/pathutil/hash_path.go create mode 100644 utils/pathutil/hash_path_test.go diff --git a/api/custom_response.go b/api/custom_response.go index 9211856e..5c781d7f 100644 --- a/api/custom_response.go +++ b/api/custom_response.go @@ -9,17 +9,22 @@ type JiaozifsResponse struct { http.ResponseWriter } -func (response *JiaozifsResponse) RespJSON(v interface{}) { +func (response *JiaozifsResponse) JSON(v interface{}) { response.Header().Set("Content-Type", "application/json") response.WriteHeader(http.StatusOK) err := json.NewEncoder(response).Encode(v) if err != nil { - response.RespError(err) + response.Error(err) return } } -func (response *JiaozifsResponse) RespError(err error) { - response.WriteHeader(http.StatusOK) +func (response *JiaozifsResponse) Error(err error) { + response.WriteHeader(http.StatusInternalServerError) _, _ = response.Write([]byte(err.Error())) } + +func (response *JiaozifsResponse) CodeMsg(code int, msg string) { + response.WriteHeader(code) + _, _ = response.Write([]byte(msg)) +} diff --git a/api/swagger.yml b/api/swagger.yml index 5fab774d..8f929dc2 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -139,9 +139,9 @@ components: properties: path: type: string - path_type: - type: string - enum: [common_prefix, object] + path_mode: + type: integer + format: uint32 physical_address: type: string description: | @@ -233,13 +233,24 @@ paths: schema: $ref: "#/components/schemas/VersionResult" - /repositories/{repository}/objects: + /object/{user}/{repository}: parameters: + - in: path + name: user + required: true + schema: + type: string - in: path name: repository required: true schema: type: string + - in: query + name: branch + description: branch to the ref + required: true + schema: + type: string - in: query name: path description: relative to the ref diff --git a/api/tmpls/chi/chi-interface.tmpl b/api/tmpls/chi/chi-interface.tmpl index 72e8cf76..11f1b0af 100644 --- a/api/tmpls/chi/chi-interface.tmpl +++ b/api/tmpls/chi/chi-interface.tmpl @@ -2,7 +2,7 @@ type ServerInterface interface { {{range .}}{{.SummaryAsComment }} // ({{.Method}} {{.Path}}) -{{.OperationId}}(w *JiaozifsResponse, r *http.Request{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) +{{.OperationId}}(ctx context.Context, w *JiaozifsResponse, r *http.Request{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) {{end}} } @@ -11,7 +11,7 @@ type ServerInterface interface { type Unimplemented struct {} {{range .}}{{.SummaryAsComment }} // ({{.Method}} {{.Path}}) - func (_ Unimplemented) {{.OperationId}}(w *JiaozifsResponse, r *http.Request{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) { + func (_ Unimplemented) {{.OperationId}}(ctx context.Context, w *JiaozifsResponse, r *http.Request{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) { w.WriteHeader(http.StatusNotImplemented) } {{end}} diff --git a/api/tmpls/chi/chi-middleware.tmpl b/api/tmpls/chi/chi-middleware.tmpl index fc9269df..c869af00 100644 --- a/api/tmpls/chi/chi-middleware.tmpl +++ b/api/tmpls/chi/chi-middleware.tmpl @@ -174,7 +174,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ {{end}} handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.{{.OperationId}}(&JiaozifsResponse{w}, r{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}) + siw.Handler.{{.OperationId}}(r.Context(), &JiaozifsResponse{w}, r{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}) })) {{if opts.Compatibility.ApplyChiMiddlewareFirstToLast}} diff --git a/block/adapter.go b/block/adapter.go index 249c9dee..654c8736 100644 --- a/block/adapter.go +++ b/block/adapter.go @@ -75,7 +75,6 @@ type ObjectPointer struct { // contents but different option values, the first supplied option // value is retained. type PutOpts struct { - StorageClass *string // S3 storage class } // WalkOpts is a unique identifier of a prefix in the object store. diff --git a/block/azure/adapter.go b/block/azure/adapter.go index e57870fa..409344fa 100644 --- a/block/azure/adapter.go +++ b/block/azure/adapter.go @@ -9,6 +9,8 @@ import ( "strings" "time" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" @@ -489,7 +491,7 @@ func (a *Adapter) UploadPart(ctx context.Context, obj block.ObjectPointer, _ int if err != nil { return nil, err } - hashReader := block.NewHashingReader(reader, block.HashFunctionMD5) + hashReader := hash.NewHashingReader(reader, hash.HashFunctionMD5) multipartBlockWriter := NewMultipartBlockWriter(hashReader, *container, qualifiedKey.BlobURL) _, err = copyFromReader(ctx, hashReader, multipartBlockWriter, blockblob.UploadStreamOptions{ diff --git a/block/azure/multipart_block_writer.go b/block/azure/multipart_block_writer.go index 88cd4d9c..b2bcf3c9 100644 --- a/block/azure/multipart_block_writer.go +++ b/block/azure/multipart_block_writer.go @@ -11,6 +11,8 @@ import ( "strconv" "strings" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blockblob" @@ -23,7 +25,7 @@ import ( var log = logging.Logger("azure") type MultipartBlockWriter struct { - reader *block.HashingReader // the reader that would be passed to copyFromReader, this is needed in order to get size and md5 + reader *hash.HashingReader // the reader that would be passed to copyFromReader, this is needed in order to get size and md5 // to is the location we are writing our chunks to. to *blockblob.Client toIDs *blockblob.Client @@ -31,7 +33,7 @@ type MultipartBlockWriter struct { etag string } -func NewMultipartBlockWriter(reader *block.HashingReader, containerURL container.Client, objName string) *MultipartBlockWriter { +func NewMultipartBlockWriter(reader *hash.HashingReader, containerURL container.Client, objName string) *MultipartBlockWriter { return &MultipartBlockWriter{ reader: reader, to: containerURL.NewBlockBlobClient(objName), diff --git a/block/local/adapter.go b/block/local/adapter.go index 9d02c90c..0179fa79 100644 --- a/block/local/adapter.go +++ b/block/local/adapter.go @@ -17,6 +17,8 @@ import ( "strings" "time" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/google/uuid" "github.com/jiaozifs/jiaozifs/block" "github.com/jiaozifs/jiaozifs/block/params" @@ -241,7 +243,7 @@ func (l *Adapter) UploadCopyPart(ctx context.Context, sourceObj, destinationObj if err != nil { return nil, err } - md5Read := block.NewHashingReader(r, block.HashFunctionMD5) + md5Read := hash.NewHashingReader(r, hash.HashFunctionMD5) fName := uploadID + fmt.Sprintf("-%05d", partNumber) err = l.Put(ctx, block.ObjectPointer{StorageNamespace: destinationObj.StorageNamespace, Identifier: fName}, -1, md5Read, block.PutOpts{}) if err != nil { @@ -261,7 +263,7 @@ func (l *Adapter) UploadCopyPartRange(ctx context.Context, sourceObj, destinatio if err != nil { return nil, err } - md5Read := block.NewHashingReader(r, block.HashFunctionMD5) + md5Read := hash.NewHashingReader(r, hash.HashFunctionMD5) fName := uploadID + fmt.Sprintf("-%05d", partNumber) err = l.Put(ctx, block.ObjectPointer{StorageNamespace: destinationObj.StorageNamespace, Identifier: fName}, -1, md5Read, block.PutOpts{}) if err != nil { @@ -393,7 +395,7 @@ func (l *Adapter) UploadPart(ctx context.Context, obj block.ObjectPointer, _ int if err := isValidUploadID(uploadID); err != nil { return nil, err } - md5Read := block.NewHashingReader(reader, block.HashFunctionMD5) + md5Read := hash.NewHashingReader(reader, hash.HashFunctionMD5) fName := uploadID + fmt.Sprintf("-%05d", partNumber) err := l.Put(ctx, block.ObjectPointer{StorageNamespace: obj.StorageNamespace, Identifier: fName}, -1, md5Read, block.PutOpts{}) etag := hex.EncodeToString(md5Read.Md5.Sum(nil)) diff --git a/controller/coremgr.go b/controller/coremgr.go new file mode 100644 index 00000000..fd7ff231 --- /dev/null +++ b/controller/coremgr.go @@ -0,0 +1,155 @@ +package controller + +import ( + "context" + "fmt" + "io" + "os" + "strings" + + "github.com/jiaozifs/jiaozifs/utils/pathutil" + + hash2 "github.com/jiaozifs/jiaozifs/utils/hash" + + "github.com/jiaozifs/jiaozifs/block" + + "github.com/jiaozifs/jiaozifs/models/filemode" + + "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/models" +) + +var ( + ErrPathNotFound = fmt.Errorf("path not found") +) + +type CoreMgr struct { + UserRepo models.IUserRepo + Repository models.RepositoryRepo + Object models.ObjectRepo + Ref models.RefRepo +} + +func (core *CoreMgr) GetBlobByPath(ctx context.Context, treeId uuid.UUID, path string) (*models.Blob, *models.TreeEntry, error) { + rootNode, err := core.Object.TreeNode(ctx, treeId) + if err != nil { + return nil, nil, err + } + + fileSegs := strings.Split(path, "/") + pathLen := len(fileSegs) - 1 + for index, seg := range fileSegs { + for _, node := range rootNode.SubObject { + if node.Name == seg { + if index == pathLen { + //last one + if node.Mode == filemode.Regular || node.Mode == filemode.Executable { + blob, err := core.Object.Blob(ctx, node.ID) + if err != nil { + return nil, nil, err + } + return blob, &node, nil + } + return nil, nil, ErrPathNotFound + } + if node.Mode != filemode.Dir { + return nil, nil, ErrPathNotFound + } + rootNode, err = core.Object.TreeNode(ctx, treeId) + if err != nil { + return nil, nil, err + } + break + } + continue + } + return nil, nil, ErrPathNotFound + } + return nil, nil, ErrPathNotFound +} + +func (core *CoreMgr) WriteBlob(ctx context.Context, adapter block.Adapter, bucketName string, body io.Reader, contentLength int64, opts block.PutOpts) (*models.Blob, error) { + // handle the upload itself + hashReader := hash2.NewHashingReader(body, hash2.HashFunctionMD5) + hash := hash2.Hash(hashReader.Md5.Sum(nil)) + tempf, err := os.CreateTemp("", "*") + if err != nil { + return nil, err + } + _, err = io.Copy(tempf, body) + if err != nil { + return nil, err + } + + _, err = tempf.Seek(io.SeekStart, 0) + if err != nil { + return nil, err + } + + defer func() { + name := tempf.Name() + _ = tempf.Close() + _ = os.RemoveAll(name) + }() + + address := pathutil.PathOfHash(hash) + err = adapter.Put(ctx, block.ObjectPointer{ + StorageNamespace: bucketName, + IdentifierType: block.IdentifierTypeRelative, + Identifier: address, + }, contentLength, tempf, opts) + if err != nil { + return nil, err + } + + return &models.Blob{ + PhysicalAddress: address, + RelativePath: true, + Hash: hash, + Size: hashReader.CopiedSize, + }, nil +} + +func (core *CoreMgr) ApplyChange(ctx context.Context, treeID uuid.UUID, changeMode models.Action, path string) (uuid.UUID, error) { + treeNode, err := core.Object.TreeNode(ctx, treeID) + if err != nil { + return uuid.Nil, err + } + + subDir := func(ctx context.Context, tn *models.TreeNode, name string) (*models.TreeNode, error) { + for _, node := range tn.SubObject { + if node.Name == name && node.Mode == filemode.Dir { + return core.Object.TreeNode(ctx, node.ID) + } + } + return nil, ErrPathNotFound + } + subFile := func(ctx context.Context, tn *models.TreeNode, name string) (*models.Blob, error) { + for _, node := range tn.SubObject { + if node.Name == name && (node.Mode == filemode.Regular || node.Mode == filemode.Executable) { + return core.Object.Blob(ctx, node.ID) + } + } + return nil, ErrPathNotFound + } + + fileSegs := strings.Split(path, "/") + pathLen := len(fileSegs) - 1 + + for index, seg := range fileSegs { + if index == pathLen { + blob, err := subFile(ctx, treeNode, seg) + if err != nil { + return uuid.Nil, err + } + + } else { + treeNode, err = subDir(ctx, treeNode, seg) + if err != nil { + return uuid.Nil, err + } + } + return uuid.Nil, ErrPathNotFound + } + return uuid.Nil, ErrPathNotFound +} diff --git a/controller/object_ctl.go b/controller/object_ctl.go index 84cbd926..06cc78ca 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -1,7 +1,24 @@ package controller import ( + "context" + "fmt" + "io" + "mime" + "mime/multipart" "net/http" + "time" + + "github.com/jiaozifs/jiaozifs/models/filemode" + + "github.com/go-openapi/swag" + + "github.com/jiaozifs/jiaozifs/block" + + "github.com/jiaozifs/jiaozifs/utils" + "github.com/jiaozifs/jiaozifs/utils/httputil" + + "github.com/jiaozifs/jiaozifs/models" "github.com/jiaozifs/jiaozifs/api" "go.uber.org/fx" @@ -9,24 +26,193 @@ import ( type ObjectController struct { fx.In + + BlockAdapter block.Adapter + + Core *CoreMgr + StashRepo models.StashRepo + UserRepo models.IUserRepo + Repository models.RepositoryRepo + Object models.ObjectRepo + Ref models.RefRepo } -func (A ObjectController) DeleteObject(_ *api.JiaozifsResponse, r *http.Request, repository string, params api.DeleteObjectParams) { //nolint +func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, user string, repository string, params api.DeleteObjectParams) { //nolint //TODO implement me panic("implement me") } -func (A ObjectController) GetObject(_ *api.JiaozifsResponse, r *http.Request, repository string, params api.GetObjectParams) { //nolint +func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, user string, repository string, params api.GetObjectParams) { //nolint //TODO implement me panic("implement me") } -func (A ObjectController) HeadObject(_ *api.JiaozifsResponse, r *http.Request, repository string, params api.HeadObjectParams) { //nolint - //TODO implement me - panic("implement me") +func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, userName string, repository string, params api.HeadObjectParams) { //nolint + user, err := oct.UserRepo.GetByName(ctx, userName) + if err != nil { + w.Error(err) + return + } + + repo, err := oct.Repository.Get(ctx, &models.GetRepoParams{ + CreateID: user.ID, + Name: utils.String(repository), + }) + if err != nil { + w.Error(err) + return + } + + ref, err := oct.Ref.Get(ctx, &models.GetRefParams{ + RepositoryID: repo.ID, + Name: utils.String(params.Branch), + }) + if err != nil { + w.Error(err) + return + } + + commit, err := oct.Object.Commit(ctx, ref.CommitHash) + if err != nil { + w.Error(err) + return + } + + blob, treeEntry, err := oct.Core.GetBlobByPath(ctx, commit.ID, params.Path) + if err != nil { + w.Error(err) + return + } + + //lookup files + etag := httputil.ETag(blob.Hash.Hex()) + w.Header().Set("ETag", etag) + lastModified := httputil.HeaderTimestamp(blob.CreatedAt) + w.Header().Set("Last-Modified", lastModified) + w.Header().Set("Accept-Ranges", "bytes") + w.Header().Set("Content-Type", httputil.ExtensionsByType(treeEntry.Name)) + // for security, make sure the browser and any proxies en route don't cache the response + w.Header().Set("Cache-Control", "no-store, must-revalidate") + w.Header().Set("Expires", "0") + + // calculate possible byte range, if any. + if params.Range != nil { + rng, err := httputil.ParseRange(*params.Range, blob.Size) + if err != nil { + w.CodeMsg(http.StatusRequestedRangeNotSatisfiable, "") + return + } + w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", rng.StartOffset, rng.EndOffset, blob.Size)) + w.Header().Set("Content-Length", fmt.Sprintf("%d", rng.EndOffset-rng.StartOffset+1)) + w.CodeMsg(http.StatusPartialContent, "") + } else { + w.Header().Set("Content-Length", fmt.Sprint(blob.Size)) + } } -func (A ObjectController) UploadObject(_ *api.JiaozifsResponse, r *http.Request, repository string, params api.UploadObjectParams) { //nolint - //TODO implement me - panic("implement me") +func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, userName string, repository string, params api.UploadObjectParams) { //nolint + user, err := oct.UserRepo.GetByName(ctx, userName) + if err != nil { + w.Error(err) + return + } + + repo, err := oct.Repository.Get(ctx, &models.GetRepoParams{ + CreateID: user.ID, + Name: utils.String(repository), + }) + if err != nil { + w.Error(err) + return + } + + // read request body parse multipart for "content" and upload the data + contentType := r.Header.Get("Content-Type") + mediaType, p, err := mime.ParseMediaType(contentType) + if err != nil { + w.Error(err) + return + } + + var blob *models.Blob + if mediaType != "multipart/form-data" { + // handle non-multipart, direct content upload + blob, err = oct.Core.WriteBlob(ctx, oct.BlockAdapter, repo.StorageNamespace, r.Body, r.ContentLength, block.PutOpts{}) + if err != nil { + w.Error(err) + return + } + } else { + // handle multipart upload + boundary, ok := p["boundary"] + if !ok { + w.Error(err) + return + } + + contentUploaded := false + reader := multipart.NewReader(r.Body, boundary) + for !contentUploaded { + part, err := reader.NextPart() + if err == io.EOF { + break + } + if err != nil { + w.Error(err) + return + } + contentType = part.Header.Get("Content-Type") + partName := part.FormName() + if partName == "content" { + // upload the first "content" and exit the loop + blob, err = oct.Core.WriteBlob(ctx, oct.BlockAdapter, repo.StorageNamespace, part, -1, block.PutOpts{}) + if err != nil { + _ = part.Close() + w.Error(err) + return + } + contentUploaded = true + } + _ = part.Close() + } + if !contentUploaded { + err := fmt.Errorf("multipart upload missing key 'content': %w", http.ErrMissingFile) + w.Error(err) + return + } + } + + stash, err := oct.StashRepo.Get(ctx, &models.GetStashParam{ + RepositoryID: repo.ID, + CreateID: user.ID, + }) + if err != nil { + w.Error(err) + return + } + + //apply change to stash + //todo write block to stash + identifierType := block.IdentifierTypeFull + if blob.RelativePath { + identifierType = block.IdentifierTypeRelative + } + + qk, err := oct.BlockAdapter.ResolveNamespace(repo.StorageNamespace, blob.PhysicalAddress, identifierType) + if err != nil { + w.Error(err) + return + } + + response := api.ObjectStats{ + Checksum: blob.Hash.Hex(), + Mtime: time.Now().Unix(), + Path: params.Path, + PathMode: utils.Uint32(uint32(filemode.Regular)), + PhysicalAddress: qk.Format(), + SizeBytes: swag.Int64(blob.Size), + ContentType: &contentType, + Metadata: &api.ObjectUserMetadata{}, + } + w.JSON(response) } diff --git a/controller/version_ctl.go b/controller/version_ctl.go index c1cfbb30..35355782 100644 --- a/controller/version_ctl.go +++ b/controller/version_ctl.go @@ -1,6 +1,7 @@ package controller import ( + "context" "net/http" "github.com/jiaozifs/jiaozifs/api" @@ -12,14 +13,14 @@ type VersionController struct { fx.In } -func (A VersionController) GetVersion(w *api.JiaozifsResponse, _ *http.Request) { +func (A VersionController) GetVersion(_ context.Context, w *api.JiaozifsResponse, _ *http.Request) { swagger, err := api.GetSwagger() if err != nil { - w.RespError(err) + w.Error(err) return } - w.RespJSON(api.VersionResult{ + w.JSON(api.VersionResult{ ApiVersion: swagger.Info.Version, Version: version.UserVersion(), }) diff --git a/models/object.go b/models/object.go index 263bb913..12b18232 100644 --- a/models/object.go +++ b/models/object.go @@ -36,14 +36,86 @@ type Signature struct { type TreeEntry struct { Name string `bun:"name"` Mode filemode.FileMode `bun:"mode"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` Hash hash.Hash `bun:"hash"` } +type Blob struct { + bun.BaseModel `bun:"table:object"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` + Hash hash.Hash `bun:"hash,type:bytea"` + Type ObjectType `bun:"type"` + PhysicalAddress string `bun:"physical_address"` + RelativePath bool `bun:"relative_path"` + Size int64 `bun:"size"` + + CreatedAt time.Time `bun:"created_at"` + UpdatedAt time.Time `bun:"updated_at"` +} + +type TreeNode struct { + bun.BaseModel `bun:"table:object"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` + Hash hash.Hash `bun:"hash,type:bytea"` + Type ObjectType `bun:"type"` + SubObject []TreeEntry `bun:"subObj,type:jsonb"` + + CreatedAt time.Time `bun:"created_at"` + UpdatedAt time.Time `bun:"updated_at"` +} + +type Commit struct { + bun.BaseModel `bun:"table:object"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` + Hash hash.Hash `bun:"hash,type:bytea"` + Type ObjectType `bun:"type"` + //////********commit********//////// + // Author is the original author of the commit. + Author Signature `bun:"author,type:jsonb"` + // Committer is the one performing the commit, might be different from + // Author. + Committer Signature `bun:"committer,type:jsonb"` + // MergeTag is the embedded tag object when a merge commit is created by + // merging a signed tag. + MergeTag string `bun:"merge_tag"` //todo + // Message is the commit/tag message, contains arbitrary text. + Message string `bun:"message"` + // TreeHash is the hash of the root tree of the commit. + TreeId uuid.UUID `bun:"tree_id,type:uuid,notnull"` + // ParentHashes are the hashes of the parent commits of the commit. + ParentHashes []hash.Hash `bun:"parent_hashes,type:bytea[]"` + + CreatedAt time.Time `bun:"created_at"` + UpdatedAt time.Time `bun:"updated_at"` +} + +type Tag struct { + bun.BaseModel `bun:"table:object"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` + Hash hash.Hash `bun:"hash,type:bytea"` + Type ObjectType `bun:"type"` + //////********commit********//////// + // Name of the tag. + Name string `bun:"name"` + // Tagger is the one who created the tag. + Tagger Signature `bun:"tagger,type:jsonb"` + // TargetType is the object type of the target. + TargetType ObjectType `bun:"target_type"` + // Target is the hash of the target object. + Target hash.Hash `bun:"target,type:bytea"` + // Message is the tag message, contains arbitrary text. + Message string `bun:"message"` + + CreatedAt time.Time `bun:"created_at"` + UpdatedAt time.Time `bun:"updated_at"` +} + type Object struct { bun.BaseModel `bun:"table:object"` - ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` - Hash hash.Hash `bun:"hash,type:bytea"` - Size int64 `bun:"size"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` + Hash hash.Hash `bun:"hash,type:bytea"` + Type ObjectType `bun:"type"` + Size int64 `bun:"size"` //tree SubObject []TreeEntry `bun:"subObj,type:jsonb"` @@ -72,19 +144,30 @@ type Object struct { TargetType ObjectType `bun:"target_type"` // Target is the hash of the target object. Target hash.Hash `bun:"target,type:bytea"` + + CreatedAt time.Time `bun:"created_at"` + UpdatedAt time.Time `bun:"updated_at"` +} + +type GetObjParams struct { + ID uuid.UUID } type IObjectRepo interface { Insert(ctx context.Context, repo *Object) (*Object, error) - Get(ctx context.Context, id uuid.UUID) (*Object, error) + Get(ctx context.Context, params *GetObjParams) (*Object, error) Count(ctx context.Context) (int, error) List(ctx context.Context) ([]Object, error) + Blob(ctx context.Context, id uuid.UUID) (*Blob, error) + TreeNode(ctx context.Context, id uuid.UUID) (*TreeNode, error) + Commit(ctx context.Context, id uuid.UUID) (*Commit, error) + Tag(ctx context.Context, id uuid.UUID) (*Tag, error) } var _ IObjectRepo = (*ObjectRepo)(nil) type ObjectRepo struct { - *bun.DB + db *bun.DB } func NewObjectRepo(db *bun.DB) IObjectRepo { @@ -92,23 +175,49 @@ func NewObjectRepo(db *bun.DB) IObjectRepo { } func (o ObjectRepo) Insert(ctx context.Context, obj *Object) (*Object, error) { - _, err := o.DB.NewInsert().Model(obj).Exec(ctx) + _, err := o.db.NewInsert().Model(obj).Exec(ctx) if err != nil { return nil, err } return obj, nil } -func (o ObjectRepo) Get(ctx context.Context, id uuid.UUID) (*Object, error) { - obj := &Object{} - return obj, o.DB.NewSelect().Model(obj).Where("id = ?", id).Scan(ctx) +func (o ObjectRepo) Get(ctx context.Context, params *GetObjParams) (*Object, error) { + repo := &Object{} + query := o.db.NewSelect().Model(repo) + + if uuid.Nil != params.ID { + query = query.Where("id = ?", params.ID) + } + + return repo, query.Scan(ctx, repo) +} + +func (o ObjectRepo) Blob(ctx context.Context, id uuid.UUID) (*Blob, error) { + blob := &Blob{} + return blob, o.db.NewSelect().Model(blob).Where("id = ?", id).Scan(ctx) +} + +func (o ObjectRepo) TreeNode(ctx context.Context, id uuid.UUID) (*TreeNode, error) { + tree := &TreeNode{} + return tree, o.db.NewSelect().Model(tree).Where("id = ?", id).Scan(ctx) +} + +func (o ObjectRepo) Commit(ctx context.Context, id uuid.UUID) (*Commit, error) { + commit := &Commit{} + return commit, o.db.NewSelect().Model(commit).Where("id = ?", id).Scan(ctx) +} + +func (o ObjectRepo) Tag(ctx context.Context, id uuid.UUID) (*Tag, error) { + tag := &Tag{} + return tag, o.db.NewSelect().Model(tag).Where("id = ?", id).Scan(ctx) } func (o ObjectRepo) Count(ctx context.Context) (int, error) { - return o.DB.NewSelect().Model((*Object)(nil)).Count(ctx) + return o.db.NewSelect().Model((*Object)(nil)).Count(ctx) } func (o ObjectRepo) List(ctx context.Context) ([]Object, error) { obj := []Object{} - return obj, o.DB.NewSelect().Model(&obj).Scan(ctx) + return obj, o.db.NewSelect().Model(&obj).Scan(ctx) } diff --git a/models/ref.go b/models/ref.go index 889435be..f675f3a8 100644 --- a/models/ref.go +++ b/models/ref.go @@ -15,7 +15,7 @@ type Ref struct { RepositoryID uuid.UUID `bun:"repository_id,type:uuid,notnull"` CommitHash uuid.UUID `bun:"commit_hash,type:uuid,notnull"` // Path name/path of branch - Path string `bun:"path,notnull"` + Name string `bun:"name,notnull"` // Description Description string `bun:"description"` // CreateId who create this branch @@ -25,15 +25,21 @@ type Ref struct { UpdatedAt time.Time `bun:"updated_at"` } +type GetRefParams struct { + ID uuid.UUID + RepositoryID uuid.UUID + Name *string +} + type IRefRepo interface { Insert(ctx context.Context, repo *Ref) (*Ref, error) - Get(ctx context.Context, id uuid.UUID) (*Ref, error) + Get(ctx context.Context, id *GetRefParams) (*Ref, error) } var _ IRefRepo = (*RefRepo)(nil) type RefRepo struct { - *bun.DB + db *bun.DB } func NewRefRepo(db *bun.DB) IRefRepo { @@ -41,14 +47,29 @@ func NewRefRepo(db *bun.DB) IRefRepo { } func (r RefRepo) Insert(ctx context.Context, ref *Ref) (*Ref, error) { - _, err := r.DB.NewInsert().Model(ref).Exec(ctx) + _, err := r.db.NewInsert().Model(ref).Exec(ctx) if err != nil { return nil, err } return ref, nil } -func (r RefRepo) Get(ctx context.Context, id uuid.UUID) (*Ref, error) { - ref := &Ref{} - return ref, r.DB.NewSelect().Model(ref).Where("id = ?", id).Scan(ctx) +func (r RefRepo) Get(ctx context.Context, params *GetRefParams) (*Ref, error) { + repo := &Ref{} + query := r.db.NewSelect().Model(repo) + + if uuid.Nil != params.ID { + query = query.Where("id = ?", params.ID) + } + + if uuid.Nil != params.RepositoryID { + query = query.Where("create_id = ?", params.RepositoryID) + } + + if params.Name != nil { + query = query.Where("name = ?", *params.Name) + } + + return repo, query.Scan(ctx, repo) + } diff --git a/models/repository.go b/models/repository.go index 889efc12..fe56b4fc 100644 --- a/models/repository.go +++ b/models/repository.go @@ -21,15 +21,21 @@ type Repository struct { UpdatedAt time.Time `bun:"updated_at"` } +type GetRepoParams struct { + Id uuid.UUID + CreateID uuid.UUID + Name *string +} + type IRepositoryRepo interface { Insert(ctx context.Context, repo *Repository) (*Repository, error) - Get(ctx context.Context, id uuid.UUID) (*Repository, error) + Get(ctx context.Context, params *GetRepoParams) (*Repository, error) } var _ IRepositoryRepo = (*RepositoryRepo)(nil) type RepositoryRepo struct { - *bun.DB + db *bun.DB } func NewRepositoryRepo(db *bun.DB) IRepositoryRepo { @@ -37,14 +43,28 @@ func NewRepositoryRepo(db *bun.DB) IRepositoryRepo { } func (r *RepositoryRepo) Insert(ctx context.Context, repo *Repository) (*Repository, error) { - _, err := r.DB.NewInsert().Model(repo).Exec(ctx) + _, err := r.db.NewInsert().Model(repo).Exec(ctx) if err != nil { return nil, err } return repo, nil } -func (r *RepositoryRepo) Get(ctx context.Context, id uuid.UUID) (*Repository, error) { +func (r *RepositoryRepo) Get(ctx context.Context, params *GetRepoParams) (*Repository, error) { repo := &Repository{} - return repo, r.DB.NewSelect().Model(repo).Where("id = ?", id).Scan(ctx) + query := r.db.NewSelect().Model(repo) + + if uuid.Nil != params.Id { + query = query.Where("id = ?", params.Id) + } + + if uuid.Nil != params.CreateID { + query = query.Where("create_id = ?", params.CreateID) + } + + if params.Name != nil { + query = query.Where("name = ?", *params.Name) + } + + return repo, query.Scan(ctx, repo) } diff --git a/models/user.go b/models/user.go index 1104a7df..75672186 100644 --- a/models/user.go +++ b/models/user.go @@ -25,6 +25,7 @@ type User struct { type IUserRepo interface { Get(ctx context.Context, id uuid.UUID) (*User, error) + GetByName(ctx context.Context, name string) (*User, error) Insert(ctx context.Context, user *User) (*User, error) GetEPByName(ctx context.Context, name string) (string, error) @@ -35,20 +36,25 @@ type IUserRepo interface { var _ IUserRepo = (*UserRepo)(nil) type UserRepo struct { - *bun.DB + db *bun.DB } func NewUserRepo(db *bun.DB) IUserRepo { return &UserRepo{db} } +func (userRepo *UserRepo) GetByName(ctx context.Context, name string) (*User, error) { + user := &User{} + return user, userRepo.db.NewSelect().Model(user).Where("name = ?", name).Scan(ctx) +} + func (userRepo *UserRepo) Get(ctx context.Context, id uuid.UUID) (*User, error) { user := &User{} - return user, userRepo.DB.NewSelect().Model(user).Where("id = ?", id).Scan(ctx) + return user, userRepo.db.NewSelect().Model(user).Where("id = ?", id).Scan(ctx) } func (userRepo *UserRepo) Insert(ctx context.Context, user *User) (*User, error) { - _, err := userRepo.DB.NewInsert().Model(user).Exec(ctx) + _, err := userRepo.db.NewInsert().Model(user).Exec(ctx) if err != nil { return nil, err } diff --git a/models/wip.go b/models/wip.go new file mode 100644 index 00000000..6574951a --- /dev/null +++ b/models/wip.go @@ -0,0 +1,79 @@ +package models + +import ( + "context" + "time" + + "github.com/google/uuid" + "github.com/uptrace/bun" +) + +// Action values represent the kind of things a Change can represent: +// insertion, deletions or modifications of files. +type Action int + +// The set of possible actions in a change. +const ( + _ Action = iota + Insert + Delete + Modify +) + +type Stash struct { + bun.BaseModel `bun:"table:stash"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` + CurrentTreeID uuid.UUID `bun:"current_tree_id,type:uuid,notnull"` + ParentTreeID uuid.UUID `bun:"parent_tree_id,type:uuid,notnull"` + RepositoryID uuid.UUID `bun:"repository_id,type:uuid,notnull"` + CreateID uuid.UUID `bun:"create_id,type:uuid,notnull"` + CreatedAt time.Time `bun:"created_at"` + UpdatedAt time.Time `bun:"updated_at"` +} + +type GetStashParam struct { + ID uuid.UUID + CreateID uuid.UUID + RepositoryID uuid.UUID +} + +type IStashRepo interface { + Insert(ctx context.Context, repo *Stash) (*Stash, error) + Get(ctx context.Context, params *GetStashParam) (*Stash, error) +} + +var _ IStashRepo = (*StashRepo)(nil) + +type StashRepo struct { + db *bun.DB +} + +func NewStashRepo(db *bun.DB) IStashRepo { + return &StashRepo{db} +} + +func (s *StashRepo) Insert(ctx context.Context, repo *Stash) (*Stash, error) { + _, err := s.db.NewInsert().Model(repo).Exec(ctx) + if err != nil { + return nil, err + } + return repo, nil +} + +func (s *StashRepo) Get(ctx context.Context, params *GetStashParam) (*Stash, error) { + repo := &Stash{} + query := s.db.NewSelect().Model(repo) + + if uuid.Nil != params.ID { + query = query.Where("id = ?", params.ID) + } + + if uuid.Nil != params.CreateID { + query = query.Where("create_id = ?", params.CreateID) + } + + if uuid.Nil != params.RepositoryID { + query = query.Where("repository_id = ?", params.RepositoryID) + } + return repo, query.Scan(ctx, repo) +} diff --git a/utils/convert_types.go b/utils/convert_types.go new file mode 100644 index 00000000..cf8c4281 --- /dev/null +++ b/utils/convert_types.go @@ -0,0 +1,918 @@ +package utils + +import "time" + +// String returns a pointer to the string value passed in. +func String(v string) *string { + return &v +} + +// StringValue returns the value of the string pointer passed in or +// "" if the pointer is nil. +func StringValue(v *string) string { + if v != nil { + return *v + } + return "" +} + +// StringSlice converts a slice of string values into a slice of +// string pointers +func StringSlice(src []string) []*string { + dst := make([]*string, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// StringValueSlice converts a slice of string pointers into a slice of +// string values +func StringValueSlice(src []*string) []string { + dst := make([]string, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// StringMap converts a string map of string values into a string +// map of string pointers +func StringMap(src map[string]string) map[string]*string { + dst := make(map[string]*string) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// StringValueMap converts a string map of string pointers into a string +// map of string values +func StringValueMap(src map[string]*string) map[string]string { + dst := make(map[string]string) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Bool returns a pointer to the bool value passed in. +func Bool(v bool) *bool { + return &v +} + +// BoolValue returns the value of the bool pointer passed in or +// false if the pointer is nil. +func BoolValue(v *bool) bool { + if v != nil { + return *v + } + return false +} + +// BoolSlice converts a slice of bool values into a slice of +// bool pointers +func BoolSlice(src []bool) []*bool { + dst := make([]*bool, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// BoolValueSlice converts a slice of bool pointers into a slice of +// bool values +func BoolValueSlice(src []*bool) []bool { + dst := make([]bool, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// BoolMap converts a string map of bool values into a string +// map of bool pointers +func BoolMap(src map[string]bool) map[string]*bool { + dst := make(map[string]*bool) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// BoolValueMap converts a string map of bool pointers into a string +// map of bool values +func BoolValueMap(src map[string]*bool) map[string]bool { + dst := make(map[string]bool) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Int returns a pointer to the int value passed in. +func Int(v int) *int { + return &v +} + +// IntValue returns the value of the int pointer passed in or +// 0 if the pointer is nil. +func IntValue(v *int) int { + if v != nil { + return *v + } + return 0 +} + +// IntSlice converts a slice of int values into a slice of +// int pointers +func IntSlice(src []int) []*int { + dst := make([]*int, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// IntValueSlice converts a slice of int pointers into a slice of +// int values +func IntValueSlice(src []*int) []int { + dst := make([]int, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// IntMap converts a string map of int values into a string +// map of int pointers +func IntMap(src map[string]int) map[string]*int { + dst := make(map[string]*int) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// IntValueMap converts a string map of int pointers into a string +// map of int values +func IntValueMap(src map[string]*int) map[string]int { + dst := make(map[string]int) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Uint returns a pointer to the uint value passed in. +func Uint(v uint) *uint { + return &v +} + +// UintValue returns the value of the uint pointer passed in or +// 0 if the pointer is nil. +func UintValue(v *uint) uint { + if v != nil { + return *v + } + return 0 +} + +// UintSlice converts a slice of uint values uinto a slice of +// uint pointers +func UintSlice(src []uint) []*uint { + dst := make([]*uint, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// UintValueSlice converts a slice of uint pointers uinto a slice of +// uint values +func UintValueSlice(src []*uint) []uint { + dst := make([]uint, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// UintMap converts a string map of uint values uinto a string +// map of uint pointers +func UintMap(src map[string]uint) map[string]*uint { + dst := make(map[string]*uint) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// UintValueMap converts a string map of uint pointers uinto a string +// map of uint values +func UintValueMap(src map[string]*uint) map[string]uint { + dst := make(map[string]uint) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Int8 returns a pointer to the int8 value passed in. +func Int8(v int8) *int8 { + return &v +} + +// Int8Value returns the value of the int8 pointer passed in or +// 0 if the pointer is nil. +func Int8Value(v *int8) int8 { + if v != nil { + return *v + } + return 0 +} + +// Int8Slice converts a slice of int8 values into a slice of +// int8 pointers +func Int8Slice(src []int8) []*int8 { + dst := make([]*int8, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// Int8ValueSlice converts a slice of int8 pointers into a slice of +// int8 values +func Int8ValueSlice(src []*int8) []int8 { + dst := make([]int8, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// Int8Map converts a string map of int8 values into a string +// map of int8 pointers +func Int8Map(src map[string]int8) map[string]*int8 { + dst := make(map[string]*int8) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// Int8ValueMap converts a string map of int8 pointers into a string +// map of int8 values +func Int8ValueMap(src map[string]*int8) map[string]int8 { + dst := make(map[string]int8) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Int16 returns a pointer to the int16 value passed in. +func Int16(v int16) *int16 { + return &v +} + +// Int16Value returns the value of the int16 pointer passed in or +// 0 if the pointer is nil. +func Int16Value(v *int16) int16 { + if v != nil { + return *v + } + return 0 +} + +// Int16Slice converts a slice of int16 values into a slice of +// int16 pointers +func Int16Slice(src []int16) []*int16 { + dst := make([]*int16, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// Int16ValueSlice converts a slice of int16 pointers into a slice of +// int16 values +func Int16ValueSlice(src []*int16) []int16 { + dst := make([]int16, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// Int16Map converts a string map of int16 values into a string +// map of int16 pointers +func Int16Map(src map[string]int16) map[string]*int16 { + dst := make(map[string]*int16) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// Int16ValueMap converts a string map of int16 pointers into a string +// map of int16 values +func Int16ValueMap(src map[string]*int16) map[string]int16 { + dst := make(map[string]int16) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Int32 returns a pointer to the int32 value passed in. +func Int32(v int32) *int32 { + return &v +} + +// Int32Value returns the value of the int32 pointer passed in or +// 0 if the pointer is nil. +func Int32Value(v *int32) int32 { + if v != nil { + return *v + } + return 0 +} + +// Int32Slice converts a slice of int32 values into a slice of +// int32 pointers +func Int32Slice(src []int32) []*int32 { + dst := make([]*int32, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// Int32ValueSlice converts a slice of int32 pointers into a slice of +// int32 values +func Int32ValueSlice(src []*int32) []int32 { + dst := make([]int32, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// Int32Map converts a string map of int32 values into a string +// map of int32 pointers +func Int32Map(src map[string]int32) map[string]*int32 { + dst := make(map[string]*int32) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// Int32ValueMap converts a string map of int32 pointers into a string +// map of int32 values +func Int32ValueMap(src map[string]*int32) map[string]int32 { + dst := make(map[string]int32) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Int64 returns a pointer to the int64 value passed in. +func Int64(v int64) *int64 { + return &v +} + +// Int64Value returns the value of the int64 pointer passed in or +// 0 if the pointer is nil. +func Int64Value(v *int64) int64 { + if v != nil { + return *v + } + return 0 +} + +// Int64Slice converts a slice of int64 values into a slice of +// int64 pointers +func Int64Slice(src []int64) []*int64 { + dst := make([]*int64, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// Int64ValueSlice converts a slice of int64 pointers into a slice of +// int64 values +func Int64ValueSlice(src []*int64) []int64 { + dst := make([]int64, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// Int64Map converts a string map of int64 values into a string +// map of int64 pointers +func Int64Map(src map[string]int64) map[string]*int64 { + dst := make(map[string]*int64) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// Int64ValueMap converts a string map of int64 pointers into a string +// map of int64 values +func Int64ValueMap(src map[string]*int64) map[string]int64 { + dst := make(map[string]int64) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Uint8 returns a pointer to the uint8 value passed in. +func Uint8(v uint8) *uint8 { + return &v +} + +// Uint8Value returns the value of the uint8 pointer passed in or +// 0 if the pointer is nil. +func Uint8Value(v *uint8) uint8 { + if v != nil { + return *v + } + return 0 +} + +// Uint8Slice converts a slice of uint8 values into a slice of +// uint8 pointers +func Uint8Slice(src []uint8) []*uint8 { + dst := make([]*uint8, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// Uint8ValueSlice converts a slice of uint8 pointers into a slice of +// uint8 values +func Uint8ValueSlice(src []*uint8) []uint8 { + dst := make([]uint8, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// Uint8Map converts a string map of uint8 values into a string +// map of uint8 pointers +func Uint8Map(src map[string]uint8) map[string]*uint8 { + dst := make(map[string]*uint8) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// Uint8ValueMap converts a string map of uint8 pointers into a string +// map of uint8 values +func Uint8ValueMap(src map[string]*uint8) map[string]uint8 { + dst := make(map[string]uint8) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Uint16 returns a pointer to the uint16 value passed in. +func Uint16(v uint16) *uint16 { + return &v +} + +// Uint16Value returns the value of the uint16 pointer passed in or +// 0 if the pointer is nil. +func Uint16Value(v *uint16) uint16 { + if v != nil { + return *v + } + return 0 +} + +// Uint16Slice converts a slice of uint16 values into a slice of +// uint16 pointers +func Uint16Slice(src []uint16) []*uint16 { + dst := make([]*uint16, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// Uint16ValueSlice converts a slice of uint16 pointers into a slice of +// uint16 values +func Uint16ValueSlice(src []*uint16) []uint16 { + dst := make([]uint16, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// Uint16Map converts a string map of uint16 values into a string +// map of uint16 pointers +func Uint16Map(src map[string]uint16) map[string]*uint16 { + dst := make(map[string]*uint16) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// Uint16ValueMap converts a string map of uint16 pointers into a string +// map of uint16 values +func Uint16ValueMap(src map[string]*uint16) map[string]uint16 { + dst := make(map[string]uint16) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Uint32 returns a pointer to the uint32 value passed in. +func Uint32(v uint32) *uint32 { + return &v +} + +// Uint32Value returns the value of the uint32 pointer passed in or +// 0 if the pointer is nil. +func Uint32Value(v *uint32) uint32 { + if v != nil { + return *v + } + return 0 +} + +// Uint32Slice converts a slice of uint32 values into a slice of +// uint32 pointers +func Uint32Slice(src []uint32) []*uint32 { + dst := make([]*uint32, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// Uint32ValueSlice converts a slice of uint32 pointers into a slice of +// uint32 values +func Uint32ValueSlice(src []*uint32) []uint32 { + dst := make([]uint32, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// Uint32Map converts a string map of uint32 values into a string +// map of uint32 pointers +func Uint32Map(src map[string]uint32) map[string]*uint32 { + dst := make(map[string]*uint32) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// Uint32ValueMap converts a string map of uint32 pointers into a string +// map of uint32 values +func Uint32ValueMap(src map[string]*uint32) map[string]uint32 { + dst := make(map[string]uint32) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Uint64 returns a pointer to the uint64 value passed in. +func Uint64(v uint64) *uint64 { + return &v +} + +// Uint64Value returns the value of the uint64 pointer passed in or +// 0 if the pointer is nil. +func Uint64Value(v *uint64) uint64 { + if v != nil { + return *v + } + return 0 +} + +// Uint64Slice converts a slice of uint64 values into a slice of +// uint64 pointers +func Uint64Slice(src []uint64) []*uint64 { + dst := make([]*uint64, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// Uint64ValueSlice converts a slice of uint64 pointers into a slice of +// uint64 values +func Uint64ValueSlice(src []*uint64) []uint64 { + dst := make([]uint64, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// Uint64Map converts a string map of uint64 values into a string +// map of uint64 pointers +func Uint64Map(src map[string]uint64) map[string]*uint64 { + dst := make(map[string]*uint64) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// Uint64ValueMap converts a string map of uint64 pointers into a string +// map of uint64 values +func Uint64ValueMap(src map[string]*uint64) map[string]uint64 { + dst := make(map[string]uint64) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Float32 returns a pointer to the float32 value passed in. +func Float32(v float32) *float32 { + return &v +} + +// Float32Value returns the value of the float32 pointer passed in or +// 0 if the pointer is nil. +func Float32Value(v *float32) float32 { + if v != nil { + return *v + } + return 0 +} + +// Float32Slice converts a slice of float32 values into a slice of +// float32 pointers +func Float32Slice(src []float32) []*float32 { + dst := make([]*float32, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// Float32ValueSlice converts a slice of float32 pointers into a slice of +// float32 values +func Float32ValueSlice(src []*float32) []float32 { + dst := make([]float32, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// Float32Map converts a string map of float32 values into a string +// map of float32 pointers +func Float32Map(src map[string]float32) map[string]*float32 { + dst := make(map[string]*float32) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// Float32ValueMap converts a string map of float32 pointers into a string +// map of float32 values +func Float32ValueMap(src map[string]*float32) map[string]float32 { + dst := make(map[string]float32) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Float64 returns a pointer to the float64 value passed in. +func Float64(v float64) *float64 { + return &v +} + +// Float64Value returns the value of the float64 pointer passed in or +// 0 if the pointer is nil. +func Float64Value(v *float64) float64 { + if v != nil { + return *v + } + return 0 +} + +// Float64Slice converts a slice of float64 values into a slice of +// float64 pointers +func Float64Slice(src []float64) []*float64 { + dst := make([]*float64, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// Float64ValueSlice converts a slice of float64 pointers into a slice of +// float64 values +func Float64ValueSlice(src []*float64) []float64 { + dst := make([]float64, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// Float64Map converts a string map of float64 values into a string +// map of float64 pointers +func Float64Map(src map[string]float64) map[string]*float64 { + dst := make(map[string]*float64) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// Float64ValueMap converts a string map of float64 pointers into a string +// map of float64 values +func Float64ValueMap(src map[string]*float64) map[string]float64 { + dst := make(map[string]float64) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Time returns a pointer to the time.Time value passed in. +func Time(v time.Time) *time.Time { + return &v +} + +// TimeValue returns the value of the time.Time pointer passed in or +// time.Time{} if the pointer is nil. +func TimeValue(v *time.Time) time.Time { + if v != nil { + return *v + } + return time.Time{} +} + +// SecondsTimeValue converts an int64 pointer to a time.Time value +// representing seconds since Epoch or time.Time{} if the pointer is nil. +func SecondsTimeValue(v *int64) time.Time { + if v != nil { + return time.Unix((*v / 1000), 0) + } + return time.Time{} +} + +// MillisecondsTimeValue converts an int64 pointer to a time.Time value +// representing milliseconds sinch Epoch or time.Time{} if the pointer is nil. +func MillisecondsTimeValue(v *int64) time.Time { + if v != nil { + return time.Unix(0, (*v * 1000000)) + } + return time.Time{} +} + +// TimeUnixMilli returns a Unix timestamp in milliseconds from "January 1, 1970 UTC". +// The result is undefined if the Unix time cannot be represented by an int64. +// Which includes calling TimeUnixMilli on a zero Time is undefined. +// +// This utility is useful for service API's such as CloudWatch Logs which require +// their unix time values to be in milliseconds. +// +// See Go stdlib https://golang.org/pkg/time/#Time.UnixNano for more information. +func TimeUnixMilli(t time.Time) int64 { + return t.UnixNano() / int64(time.Millisecond/time.Nanosecond) +} + +// TimeSlice converts a slice of time.Time values into a slice of +// time.Time pointers +func TimeSlice(src []time.Time) []*time.Time { + dst := make([]*time.Time, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// TimeValueSlice converts a slice of time.Time pointers into a slice of +// time.Time values +func TimeValueSlice(src []*time.Time) []time.Time { + dst := make([]time.Time, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// TimeMap converts a string map of time.Time values into a string +// map of time.Time pointers +func TimeMap(src map[string]time.Time) map[string]*time.Time { + dst := make(map[string]*time.Time) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// TimeValueMap converts a string map of time.Time pointers into a string +// map of time.Time values +func TimeValueMap(src map[string]*time.Time) map[string]time.Time { + dst := make(map[string]time.Time) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} diff --git a/utils/convert_types_test.go b/utils/convert_types_test.go new file mode 100644 index 00000000..95308850 --- /dev/null +++ b/utils/convert_types_test.go @@ -0,0 +1,1532 @@ +package utils + +import ( + "reflect" + "testing" + "time" +) + +var testCasesStringSlice = [][]string{ + {"a", "b", "c", "d", "e"}, + {"a", "b", "", "", "e"}, +} + +func TestStringSlice(t *testing.T) { + for idx, in := range testCasesStringSlice { + if in == nil { + continue + } + out := StringSlice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := StringValueSlice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesStringValueSlice = [][]*string{ + {String("a"), String("b"), nil, String("c")}, +} + +func TestStringValueSlice(t *testing.T) { + for idx, in := range testCasesStringValueSlice { + if in == nil { + continue + } + out := StringValueSlice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if in[i] == nil { + if out[i] != "" { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := *(in[i]), out[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + + out2 := StringSlice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out2 { + if in[i] == nil { + if *(out2[i]) != "" { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := *in[i], *out2[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + } +} + +var testCasesStringMap = []map[string]string{ + {"a": "1", "b": "2", "c": "3"}, +} + +func TestStringMap(t *testing.T) { + for idx, in := range testCasesStringMap { + if in == nil { + continue + } + out := StringMap(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := StringValueMap(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesBoolSlice = [][]bool{ + {true, true, false, false}, +} + +func TestBoolSlice(t *testing.T) { + for idx, in := range testCasesBoolSlice { + if in == nil { + continue + } + out := BoolSlice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := BoolValueSlice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesBoolValueSlice = [][]*bool{} + +func TestBoolValueSlice(t *testing.T) { + for idx, in := range testCasesBoolValueSlice { + if in == nil { + continue + } + out := BoolValueSlice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if in[i] == nil { + if out[i] { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := *(in[i]), out[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + + out2 := BoolSlice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out2 { + if in[i] == nil { + if *(out2[i]) { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := in[i], out2[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + } +} + +var testCasesBoolMap = []map[string]bool{ + {"a": true, "b": false, "c": true}, +} + +func TestBoolMap(t *testing.T) { + for idx, in := range testCasesBoolMap { + if in == nil { + continue + } + out := BoolMap(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := BoolValueMap(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesUintSlice = [][]uint{ + {1, 2, 3, 4}, +} + +func TestUintSlice(t *testing.T) { + for idx, in := range testCasesUintSlice { + if in == nil { + continue + } + out := UintSlice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := UintValueSlice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesUintValueSlice = [][]*uint{} + +func TestUintValueSlice(t *testing.T) { + for idx, in := range testCasesUintValueSlice { + if in == nil { + continue + } + out := UintValueSlice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if in[i] == nil { + if out[i] != 0 { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := *(in[i]), out[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + + out2 := UintSlice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out2 { + if in[i] == nil { + if *(out2[i]) != 0 { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := in[i], out2[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + } +} + +var testCasesUintMap = []map[string]uint{ + {"a": 3, "b": 2, "c": 1}, +} + +func TestUintMap(t *testing.T) { + for idx, in := range testCasesUintMap { + if in == nil { + continue + } + out := UintMap(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := UintValueMap(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesIntSlice = [][]int{ + {1, 2, 3, 4}, +} + +func TestIntSlice(t *testing.T) { + for idx, in := range testCasesIntSlice { + if in == nil { + continue + } + out := IntSlice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := IntValueSlice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesIntValueSlice = [][]*int{} + +func TestIntValueSlice(t *testing.T) { + for idx, in := range testCasesIntValueSlice { + if in == nil { + continue + } + out := IntValueSlice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if in[i] == nil { + if out[i] != 0 { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := *(in[i]), out[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + + out2 := IntSlice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out2 { + if in[i] == nil { + if *(out2[i]) != 0 { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := in[i], out2[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + } +} + +var testCasesIntMap = []map[string]int{ + {"a": 3, "b": 2, "c": 1}, +} + +func TestIntMap(t *testing.T) { + for idx, in := range testCasesIntMap { + if in == nil { + continue + } + out := IntMap(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := IntValueMap(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesInt8Slice = [][]int8{ + {1, 2, 3, 4}, +} + +func TestInt8Slice(t *testing.T) { + for idx, in := range testCasesInt8Slice { + if in == nil { + continue + } + out := Int8Slice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := Int8ValueSlice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesInt8ValueSlice = [][]*int8{} + +func TestInt8ValueSlice(t *testing.T) { + for idx, in := range testCasesInt8ValueSlice { + if in == nil { + continue + } + out := Int8ValueSlice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if in[i] == nil { + if out[i] != 0 { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := *(in[i]), out[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + + out2 := Int8Slice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out2 { + if in[i] == nil { + if *(out2[i]) != 0 { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := in[i], out2[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + } +} + +var testCasesInt8Map = []map[string]int8{ + {"a": 3, "b": 2, "c": 1}, +} + +func TestInt8Map(t *testing.T) { + for idx, in := range testCasesInt8Map { + if in == nil { + continue + } + out := Int8Map(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := Int8ValueMap(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesInt16Slice = [][]int16{ + {1, 2, 3, 4}, +} + +func TestInt16Slice(t *testing.T) { + for idx, in := range testCasesInt16Slice { + if in == nil { + continue + } + out := Int16Slice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := Int16ValueSlice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesInt16ValueSlice = [][]*int16{} + +func TestInt16ValueSlice(t *testing.T) { + for idx, in := range testCasesInt16ValueSlice { + if in == nil { + continue + } + out := Int16ValueSlice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if in[i] == nil { + if out[i] != 0 { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := *(in[i]), out[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + + out2 := Int16Slice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out2 { + if in[i] == nil { + if *(out2[i]) != 0 { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := in[i], out2[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + } +} + +var testCasesInt16Map = []map[string]int16{ + {"a": 3, "b": 2, "c": 1}, +} + +func TestInt16Map(t *testing.T) { + for idx, in := range testCasesInt16Map { + if in == nil { + continue + } + out := Int16Map(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := Int16ValueMap(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesInt32Slice = [][]int32{ + {1, 2, 3, 4}, +} + +func TestInt32Slice(t *testing.T) { + for idx, in := range testCasesInt32Slice { + if in == nil { + continue + } + out := Int32Slice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := Int32ValueSlice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesInt32ValueSlice = [][]*int32{} + +func TestInt32ValueSlice(t *testing.T) { + for idx, in := range testCasesInt32ValueSlice { + if in == nil { + continue + } + out := Int32ValueSlice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if in[i] == nil { + if out[i] != 0 { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := *(in[i]), out[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + + out2 := Int32Slice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out2 { + if in[i] == nil { + if *(out2[i]) != 0 { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := in[i], out2[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + } +} + +var testCasesInt32Map = []map[string]int32{ + {"a": 3, "b": 2, "c": 1}, +} + +func TestInt32Map(t *testing.T) { + for idx, in := range testCasesInt32Map { + if in == nil { + continue + } + out := Int32Map(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := Int32ValueMap(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesInt64Slice = [][]int64{ + {1, 2, 3, 4}, +} + +func TestInt64Slice(t *testing.T) { + for idx, in := range testCasesInt64Slice { + if in == nil { + continue + } + out := Int64Slice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := Int64ValueSlice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesInt64ValueSlice = [][]*int64{} + +func TestInt64ValueSlice(t *testing.T) { + for idx, in := range testCasesInt64ValueSlice { + if in == nil { + continue + } + out := Int64ValueSlice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if in[i] == nil { + if out[i] != 0 { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := *(in[i]), out[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + + out2 := Int64Slice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out2 { + if in[i] == nil { + if *(out2[i]) != 0 { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := in[i], out2[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + } +} + +var testCasesInt64Map = []map[string]int64{ + {"a": 3, "b": 2, "c": 1}, +} + +func TestInt64Map(t *testing.T) { + for idx, in := range testCasesInt64Map { + if in == nil { + continue + } + out := Int64Map(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := Int64ValueMap(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesUint8Slice = [][]uint8{ + {1, 2, 3, 4}, +} + +func TestUint8Slice(t *testing.T) { + for idx, in := range testCasesUint8Slice { + if in == nil { + continue + } + out := Uint8Slice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := Uint8ValueSlice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesUint8ValueSlice = [][]*uint8{} + +func TestUint8ValueSlice(t *testing.T) { + for idx, in := range testCasesUint8ValueSlice { + if in == nil { + continue + } + out := Uint8ValueSlice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if in[i] == nil { + if out[i] != 0 { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := *(in[i]), out[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + + out2 := Uint8Slice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out2 { + if in[i] == nil { + if *(out2[i]) != 0 { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := in[i], out2[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + } +} + +var testCasesUint8Map = []map[string]uint8{ + {"a": 3, "b": 2, "c": 1}, +} + +func TestUint8Map(t *testing.T) { + for idx, in := range testCasesUint8Map { + if in == nil { + continue + } + out := Uint8Map(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := Uint8ValueMap(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesUint16Slice = [][]uint16{ + {1, 2, 3, 4}, +} + +func TestUint16Slice(t *testing.T) { + for idx, in := range testCasesUint16Slice { + if in == nil { + continue + } + out := Uint16Slice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := Uint16ValueSlice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesUint16ValueSlice = [][]*uint16{} + +func TestUint16ValueSlice(t *testing.T) { + for idx, in := range testCasesUint16ValueSlice { + if in == nil { + continue + } + out := Uint16ValueSlice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if in[i] == nil { + if out[i] != 0 { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := *(in[i]), out[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + + out2 := Uint16Slice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out2 { + if in[i] == nil { + if *(out2[i]) != 0 { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := in[i], out2[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + } +} + +var testCasesUint16Map = []map[string]uint16{ + {"a": 3, "b": 2, "c": 1}, +} + +func TestUint16Map(t *testing.T) { + for idx, in := range testCasesUint16Map { + if in == nil { + continue + } + out := Uint16Map(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := Uint16ValueMap(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesUint32Slice = [][]uint32{ + {1, 2, 3, 4}, +} + +func TestUint32Slice(t *testing.T) { + for idx, in := range testCasesUint32Slice { + if in == nil { + continue + } + out := Uint32Slice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := Uint32ValueSlice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesUint32ValueSlice = [][]*uint32{} + +func TestUint32ValueSlice(t *testing.T) { + for idx, in := range testCasesUint32ValueSlice { + if in == nil { + continue + } + out := Uint32ValueSlice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if in[i] == nil { + if out[i] != 0 { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := *(in[i]), out[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + + out2 := Uint32Slice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out2 { + if in[i] == nil { + if *(out2[i]) != 0 { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := in[i], out2[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + } +} + +var testCasesUint32Map = []map[string]uint32{ + {"a": 3, "b": 2, "c": 1}, +} + +func TestUint32Map(t *testing.T) { + for idx, in := range testCasesUint32Map { + if in == nil { + continue + } + out := Uint32Map(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := Uint32ValueMap(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesUint64Slice = [][]uint64{ + {1, 2, 3, 4}, +} + +func TestUint64Slice(t *testing.T) { + for idx, in := range testCasesUint64Slice { + if in == nil { + continue + } + out := Uint64Slice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := Uint64ValueSlice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesUint64ValueSlice = [][]*uint64{} + +func TestUint64ValueSlice(t *testing.T) { + for idx, in := range testCasesUint64ValueSlice { + if in == nil { + continue + } + out := Uint64ValueSlice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if in[i] == nil { + if out[i] != 0 { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := *(in[i]), out[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + + out2 := Uint64Slice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out2 { + if in[i] == nil { + if *(out2[i]) != 0 { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := in[i], out2[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + } +} + +var testCasesUint64Map = []map[string]uint64{ + {"a": 3, "b": 2, "c": 1}, +} + +func TestUint64Map(t *testing.T) { + for idx, in := range testCasesUint64Map { + if in == nil { + continue + } + out := Uint64Map(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := Uint64ValueMap(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesFloat32Slice = [][]float32{ + {1, 2, 3, 4}, +} + +func TestFloat32Slice(t *testing.T) { + for idx, in := range testCasesFloat32Slice { + if in == nil { + continue + } + out := Float32Slice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := Float32ValueSlice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesFloat32ValueSlice = [][]*float32{} + +func TestFloat32ValueSlice(t *testing.T) { + for idx, in := range testCasesFloat32ValueSlice { + if in == nil { + continue + } + out := Float32ValueSlice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if in[i] == nil { + if out[i] != 0 { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := *(in[i]), out[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + + out2 := Float32Slice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out2 { + if in[i] == nil { + if *(out2[i]) != 0 { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := in[i], out2[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + } +} + +var testCasesFloat32Map = []map[string]float32{ + {"a": 3, "b": 2, "c": 1}, +} + +func TestFloat32Map(t *testing.T) { + for idx, in := range testCasesFloat32Map { + if in == nil { + continue + } + out := Float32Map(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := Float32ValueMap(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesFloat64Slice = [][]float64{ + {1, 2, 3, 4}, +} + +func TestFloat64Slice(t *testing.T) { + for idx, in := range testCasesFloat64Slice { + if in == nil { + continue + } + out := Float64Slice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := Float64ValueSlice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesFloat64ValueSlice = [][]*float64{} + +func TestFloat64ValueSlice(t *testing.T) { + for idx, in := range testCasesFloat64ValueSlice { + if in == nil { + continue + } + out := Float64ValueSlice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if in[i] == nil { + if out[i] != 0 { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := *(in[i]), out[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + + out2 := Float64Slice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out2 { + if in[i] == nil { + if *(out2[i]) != 0 { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := in[i], out2[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + } +} + +var testCasesFloat64Map = []map[string]float64{ + {"a": 3, "b": 2, "c": 1}, +} + +func TestFloat64Map(t *testing.T) { + for idx, in := range testCasesFloat64Map { + if in == nil { + continue + } + out := Float64Map(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := Float64ValueMap(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesTimeSlice = [][]time.Time{ + {time.Now(), time.Now().AddDate(100, 0, 0)}, +} + +func TestTimeSlice(t *testing.T) { + for idx, in := range testCasesTimeSlice { + if in == nil { + continue + } + out := TimeSlice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := TimeValueSlice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +var testCasesTimeValueSlice = [][]*time.Time{} + +func TestTimeValueSlice(t *testing.T) { + for idx, in := range testCasesTimeValueSlice { + if in == nil { + continue + } + out := TimeValueSlice(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if in[i] == nil { + if !out[i].IsZero() { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := *(in[i]), out[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + + out2 := TimeSlice(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out2 { + if in[i] == nil { + if !(out2[i]).IsZero() { + t.Errorf("Unexpected value at idx %d", idx) + } + } else { + if e, a := in[i], out2[i]; e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + } + } +} + +var testCasesTimeMap = []map[string]time.Time{ + {"a": time.Now().AddDate(-100, 0, 0), "b": time.Now()}, +} + +func TestTimeMap(t *testing.T) { + for idx, in := range testCasesTimeMap { + if in == nil { + continue + } + out := TimeMap(in) + if e, a := len(out), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + for i := range out { + if e, a := in[i], *(out[i]); e != a { + t.Errorf("Unexpected value at idx %d", idx) + } + } + + out2 := TimeValueMap(out) + if e, a := len(out2), len(in); e != a { + t.Errorf("Unexpected len at idx %d", idx) + } + if e, a := in, out2; !reflect.DeepEqual(e, a) { + t.Errorf("Unexpected value at idx %d", idx) + } + } +} + +type TimeValueTestCase struct { + in int64 + outSecs time.Time + outMillis time.Time +} + +var testCasesTimeValue = []TimeValueTestCase{ + { + in: int64(1501558289000), + outSecs: time.Unix(1501558289, 0), + outMillis: time.Unix(1501558289, 0), + }, + { + in: int64(1501558289001), + outSecs: time.Unix(1501558289, 0), + outMillis: time.Unix(1501558289, 1*1000000), + }, +} + +func TestSecondsTimeValue(t *testing.T) { + for idx, testCase := range testCasesTimeValue { + out := SecondsTimeValue(&testCase.in) + if e, a := testCase.outSecs, out; e != a { + t.Errorf("Unexpected value for time value at %d", idx) + } + } +} + +func TestMillisecondsTimeValue(t *testing.T) { + for idx, testCase := range testCasesTimeValue { + out := MillisecondsTimeValue(&testCase.in) + if e, a := testCase.outMillis, out; e != a { + t.Errorf("Unexpected value for time value at %d", idx) + } + } +} diff --git a/utils/hash/hash.go b/utils/hash/hash.go index 51beb812..f732352c 100644 --- a/utils/hash/hash.go +++ b/utils/hash/hash.go @@ -3,60 +3,11 @@ package hash import ( - "crypto" - "fmt" - "hash" - - "github.com/pjbgf/sha1cd" + "github.com/tmthrgd/go-hex" ) -// algos is a map of hash algorithms. -var algos = map[crypto.Hash]func() hash.Hash{} - -func init() { - reset() -} - -// reset resets the default algos value. Can be used after running tests -// that registers new algorithms to avoid side effects. -func reset() { - algos[crypto.SHA1] = sha1cd.New - algos[crypto.SHA256] = crypto.SHA256.New -} - -// RegisterHasher allows for the hash algorithm used to be overridden. -// This ensures the hash selection for go-git must be explicit, when -// overriding the default value. -func RegisterHasher(h crypto.Hash, f func() hash.Hash) error { - if f == nil { - return fmt.Errorf("cannot register hash: f is nil") - } - - switch h { - case crypto.SHA1: - algos[h] = f - case crypto.SHA256: - algos[h] = f - default: - return fmt.Errorf("unsupported hash function: %v", h) - } - return nil -} - type Hash []byte -// Hasher is the same as hash.Hash. This allows consumers -// to not having to import this package alongside "hash". -type Hasher interface { - hash.Hash -} - -// New returns a new Hash for the given hash function. -// It panics if the hash function is not registered. -func New(h crypto.Hash) Hasher { - hh, ok := algos[h] - if !ok { - panic(fmt.Sprintf("hash algorithm not registered: %v", h)) - } - return hh() +func (hash Hash) Hex() string { + return hex.EncodeToString(hash) } diff --git a/utils/hash/hash_sha1.go b/utils/hash/hash_sha1.go deleted file mode 100644 index e3cb60fe..00000000 --- a/utils/hash/hash_sha1.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build !sha256 -// +build !sha256 - -package hash - -import "crypto" - -const ( - // CryptoType defines what hash algorithm is being used. - CryptoType = crypto.SHA1 - // Size defines the amount of bytes the hash yields. - Size = 20 - // HexSize defines the strings size of the hash when represented in hexadecimal. - HexSize = 40 -) diff --git a/utils/hash/hash_sha256.go b/utils/hash/hash_sha256.go deleted file mode 100644 index 1c52b897..00000000 --- a/utils/hash/hash_sha256.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build sha256 -// +build sha256 - -package hash - -import "crypto" - -const ( - // CryptoType defines what hash algorithm is being used. - CryptoType = crypto.SHA256 - // Size defines the amount of bytes the hash yields. - Size = 32 - // HexSize defines the strings size of the hash when represented in hexadecimal. - HexSize = 64 -) diff --git a/utils/hash/hash_test.go b/utils/hash/hash_test.go deleted file mode 100644 index cb049921..00000000 --- a/utils/hash/hash_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package hash - -import ( - "crypto" - "crypto/sha1" - "crypto/sha512" - "encoding/hex" - "hash" - "strings" - "testing" -) - -func TestRegisterHash(t *testing.T) { - // Reset default hash to avoid side effects. - defer reset() - - tests := []struct { - name string - hash crypto.Hash - new func() hash.Hash - wantErr string - }{ - { - name: "sha1", - hash: crypto.SHA1, - new: sha1.New, - }, - { - name: "sha1", - hash: crypto.SHA1, - wantErr: "cannot register hash: f is nil", - }, - { - name: "sha512", - hash: crypto.SHA512, - new: sha512.New, - wantErr: "unsupported hash function", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := RegisterHasher(tt.hash, tt.new) - if tt.wantErr == "" && err != nil { - t.Errorf("unexpected error: %v", err) - } else if tt.wantErr != "" && err == nil { - t.Errorf("expected error: %v got: nil", tt.wantErr) - } else if err != nil && !strings.Contains(err.Error(), tt.wantErr) { - t.Errorf("expected error: %v got: %v", tt.wantErr, err) - } - }) - } -} - -// Verifies that the SHA1 implementation used is collision-resistant -// by default. -func TestSha1Collision(t *testing.T) { - defer reset() - - tests := []struct { - name string - content string - hash string - before func() - }{ - { - name: "sha-mbles-1: with collision detection", - content: "99040d047fe81780012000ff4b65792069732070617274206f66206120636f6c6c6973696f6e212049742773206120747261702179c61af0afcc054515d9274e7307624b1dc7fb23988bb8de8b575dba7b9eab31c1674b6d974378a827732ff5851c76a2e60772b5a47ce1eac40bb993c12d8c70e24a4f8d5fcdedc1b32c9cf19e31af2429759d42e4dfdb31719f587623ee552939b6dcdc459fca53553b70f87ede30a247ea3af6c759a2f20b320d760db64ff479084fd3ccb3cdd48362d96a9c430617caff6c36c637e53fde28417f626fec54ed7943a46e5f5730f2bb38fb1df6e0090010d00e24ad78bf92641993608e8d158a789f34c46fe1e6027f35a4cbfb827076c50eca0e8b7cca69bb2c2b790259f9bf9570dd8d4437a3115faff7c3cac09ad25266055c27104755178eaeff825a2caa2acfb5de64ce7641dc59a541a9fc9c756756e2e23dc713c8c24c9790aa6b0e38a7f55f14452a1ca2850ddd9562fd9a18ad42496aa97008f74672f68ef461eb88b09933d626b4f918749cc027fddd6c425fc4216835d0134d15285bab2cb784a4f7cbb4fb514d4bf0f6237cf00a9e9f132b9a066e6fd17f6c42987478586ff651af96747fb426b9872b9a88e4063f59bb334cc00650f83a80c42751b71974d300fc2819a2e8f1e32c1b51cb18e6bfc4db9baef675d4aaf5b1574a047f8f6dd2ec153a93412293974d928f88ced9363cfef97ce2e742bf34c96b8ef3875676fea5cca8e5f7dea0bab2413d4de00ee71ee01f162bdb6d1eafd925e6aebaae6a354ef17cf205a404fbdb12fc454d41fdd95cf2459664a2ad032d1da60a73264075d7f1e0d6c1403ae7a0d861df3fe5707188dd5e07d1589b9f8b6630553f8fc352b3e0c27da80bddba4c64020d", - hash: "4f3d9be4a472c4dae83c6314aa6c36a064c1fd14", - }, - { - name: "sha-mbles-1: with default SHA1", - content: "99040d047fe81780012000ff4b65792069732070617274206f66206120636f6c6c6973696f6e212049742773206120747261702179c61af0afcc054515d9274e7307624b1dc7fb23988bb8de8b575dba7b9eab31c1674b6d974378a827732ff5851c76a2e60772b5a47ce1eac40bb993c12d8c70e24a4f8d5fcdedc1b32c9cf19e31af2429759d42e4dfdb31719f587623ee552939b6dcdc459fca53553b70f87ede30a247ea3af6c759a2f20b320d760db64ff479084fd3ccb3cdd48362d96a9c430617caff6c36c637e53fde28417f626fec54ed7943a46e5f5730f2bb38fb1df6e0090010d00e24ad78bf92641993608e8d158a789f34c46fe1e6027f35a4cbfb827076c50eca0e8b7cca69bb2c2b790259f9bf9570dd8d4437a3115faff7c3cac09ad25266055c27104755178eaeff825a2caa2acfb5de64ce7641dc59a541a9fc9c756756e2e23dc713c8c24c9790aa6b0e38a7f55f14452a1ca2850ddd9562fd9a18ad42496aa97008f74672f68ef461eb88b09933d626b4f918749cc027fddd6c425fc4216835d0134d15285bab2cb784a4f7cbb4fb514d4bf0f6237cf00a9e9f132b9a066e6fd17f6c42987478586ff651af96747fb426b9872b9a88e4063f59bb334cc00650f83a80c42751b71974d300fc2819a2e8f1e32c1b51cb18e6bfc4db9baef675d4aaf5b1574a047f8f6dd2ec153a93412293974d928f88ced9363cfef97ce2e742bf34c96b8ef3875676fea5cca8e5f7dea0bab2413d4de00ee71ee01f162bdb6d1eafd925e6aebaae6a354ef17cf205a404fbdb12fc454d41fdd95cf2459664a2ad032d1da60a73264075d7f1e0d6c1403ae7a0d861df3fe5707188dd5e07d1589b9f8b6630553f8fc352b3e0c27da80bddba4c64020d", - hash: "8ac60ba76f1999a1ab70223f225aefdc78d4ddc0", - before: func() { - _ = RegisterHasher(crypto.SHA1, sha1.New) - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.before != nil { - tt.before() - } - - h := New(crypto.SHA1) - data, err := hex.DecodeString(tt.content) - if err != nil { - t.Fatal(err) - } - - h.Reset() - h.Write(data) - sum := h.Sum(nil) - got := hex.EncodeToString(sum) - - if tt.hash != got { - t.Errorf("\n got: %q\nwanted: %q", got, tt.hash) - } - }) - } -} diff --git a/block/hashing_reader.go b/utils/hash/hashing_reader.go similarity index 57% rename from block/hashing_reader.go rename to utils/hash/hashing_reader.go index 8907d8d2..d7a220dc 100644 --- a/block/hashing_reader.go +++ b/utils/hash/hashing_reader.go @@ -1,4 +1,4 @@ -package block +package hash import ( "crypto/md5" //nolint:gosec @@ -13,33 +13,14 @@ const ( HashFunctionSHA256 ) -type HashingReader struct { - Md5 hash.Hash - Sha256 hash.Hash - originalReader io.Reader - CopiedSize int64 -} - -func (s *HashingReader) Read(p []byte) (int, error) { - nb, err := s.originalReader.Read(p) - s.CopiedSize += int64(nb) - if s.Md5 != nil { - if _, err2 := s.Md5.Write(p[0:nb]); err2 != nil { - return nb, err2 - } - } - if s.Sha256 != nil { - if _, err2 := s.Sha256.Write(p[0:nb]); err2 != nil { - return nb, err2 - } - } - return nb, err +type Hasher struct { + Md5 hash.Hash + Sha256 hash.Hash } -func NewHashingReader(body io.Reader, hashTypes ...int) *HashingReader { - s := new(HashingReader) - s.originalReader = body - for hashType := range hashTypes { +func NewHasher(hashTypes ...int) *Hasher { + s := new(Hasher) + for _, hashType := range hashTypes { switch hashType { case HashFunctionMD5: if s.Md5 == nil { @@ -55,3 +36,40 @@ func NewHashingReader(body io.Reader, hashTypes ...int) *HashingReader { } return s } + +func (hasher *Hasher) Write(data []byte) (int, error) { + if hasher.Md5 != nil { + if _, err := hasher.Md5.Write(data); err != nil { + return 0, err + } + } + if hasher.Sha256 != nil { + if _, err := hasher.Sha256.Write(data); err != nil { + return 0, err + } + } + return len(data), nil +} + +type HashingReader struct { + *Hasher + originalReader io.Reader + CopiedSize int64 +} + +func (s *HashingReader) Read(p []byte) (int, error) { + nb, err := s.originalReader.Read(p) + if err != nil { + return nb, err + } + s.CopiedSize += int64(nb) + _, err = s.Hasher.Write(p[0:nb]) + return nb, err +} + +func NewHashingReader(body io.Reader, hashTypes ...int) *HashingReader { + s := new(HashingReader) + s.originalReader = body + s.Hasher = NewHasher(hashTypes...) + return s +} diff --git a/utils/hash/hashing_reader_test.go b/utils/hash/hashing_reader_test.go new file mode 100644 index 00000000..657c17c4 --- /dev/null +++ b/utils/hash/hashing_reader_test.go @@ -0,0 +1,65 @@ +package hash + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewHasher(t *testing.T) { + t.Run("single md5", func(t *testing.T) { + hasher := NewHasher(HashFunctionMD5) + hasher.Write([]byte{1, 2, 3, 4, 5}) + md5Hash := hasher.Md5.Sum(nil) + require.Equal(t, "7cfdd07889b3295d6a550914ab35e068", Hash(md5Hash).Hex()) + }) + + t.Run("single sha256", func(t *testing.T) { + hasher := NewHasher(HashFunctionSHA256) + hasher.Write([]byte{1, 2, 3, 4, 5}) + sha256Hash := hasher.Sha256.Sum(nil) + require.Equal(t, "74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0", Hash(sha256Hash).Hex()) + }) + + t.Run("multi sha256", func(t *testing.T) { + hasher := NewHasher(HashFunctionMD5, HashFunctionSHA256) + hasher.Write([]byte{1, 2, 3, 4, 5}) + + md5Hash := hasher.Md5.Sum(nil) + require.Equal(t, "7cfdd07889b3295d6a550914ab35e068", Hash(md5Hash).Hex()) + + sha256Hash := hasher.Sha256.Sum(nil) + require.Equal(t, "74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0", Hash(sha256Hash).Hex()) + }) +} + +func TestHashingReader_Read(t *testing.T) { + origData := []byte{1, 2, 3, 4, 5, 6, 7} + hasher := NewHashingReader(bytes.NewReader(origData), HashFunctionMD5, HashFunctionSHA256) + + buf1 := make([]byte, 5) + wLen, err := hasher.Read(buf1) + require.NoError(t, err) + require.Equal(t, 5, wLen) + + md5Hash := hasher.Md5.Sum(nil) + require.Equal(t, "7cfdd07889b3295d6a550914ab35e068", Hash(md5Hash).Hex()) + + sha256Hash := hasher.Sha256.Sum(nil) + require.Equal(t, "74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0", Hash(sha256Hash).Hex()) + + buf2 := make([]byte, 5) + wLen, err = hasher.Read(buf2) + require.NoError(t, err) + require.Equal(t, 2, wLen) + + md5Hash = hasher.Md5.Sum(nil) + require.Equal(t, "498001217bc632cb158588224d7d23c4", Hash(md5Hash).Hex()) + + sha256Hash = hasher.Sha256.Sum(nil) + require.Equal(t, "32bbe378a25091502b2baf9f7258c19444e7a43ee4593b08030acd790bd66e6a", Hash(sha256Hash).Hex()) + + require.Equal(t, origData[:5], buf1) + require.Equal(t, origData[5:], buf2[:2]) +} diff --git a/utils/httputil/client.go b/utils/httputil/client.go new file mode 100644 index 00000000..0c78fe32 --- /dev/null +++ b/utils/httputil/client.go @@ -0,0 +1,14 @@ +package httputil + +import "net/http" + +// GetRequestLakeFSClient get lakeFS client identifier from request. +// +// It extracts the data from X-Lakefs-Client header and fallback to the user-agent +func GetRequestLakeFSClient(r *http.Request) string { + id := r.Header.Get("X-Lakefs-Client") + if id == "" { + id = r.UserAgent() + } + return id +} diff --git a/utils/httputil/content_type.go b/utils/httputil/content_type.go new file mode 100644 index 00000000..d685b661 --- /dev/null +++ b/utils/httputil/content_type.go @@ -0,0 +1,16 @@ +package httputil + +import ( + "mime" + "path/filepath" + "strings" +) + +func ExtensionsByType(fileName string) string { + ext := filepath.Ext(fileName) + m := mime.TypeByExtension(ext) + if len(m) == 0 { + return "application/octet-stream" + } + return strings.Split(m, ";")[0] //remove charset part +} diff --git a/utils/httputil/content_type_test.go b/utils/httputil/content_type_test.go new file mode 100644 index 00000000..10608272 --- /dev/null +++ b/utils/httputil/content_type_test.go @@ -0,0 +1,25 @@ +package httputil + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestExtensionsByType(t *testing.T) { + + t.Run("image type", func(t *testing.T) { + mine := ExtensionsByType("m.jpg") + assert.Equal(t, "image/jpeg", mine) + }) + + t.Run("image type", func(t *testing.T) { + mine := ExtensionsByType("m.txt") + assert.Equal(t, "text/plain", mine) + }) + + t.Run("custome type", func(t *testing.T) { + mine := ExtensionsByType("m.aaaa") + assert.Equal(t, "application/octet-stream", mine) + }) +} diff --git a/utils/httputil/endpoints.go b/utils/httputil/endpoints.go new file mode 100644 index 00000000..67f96a0b --- /dev/null +++ b/utils/httputil/endpoints.go @@ -0,0 +1,45 @@ +package httputil + +import ( + "io" + "net/http" + "net/http/pprof" + "strings" +) + +var healthInfo string + +func SetHealthHandlerInfo(info string) { + healthInfo = info +} + +func ServeHealth() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = io.WriteString(w, "alive!") + if healthInfo != "" { + _, _ = io.WriteString(w, " "+healthInfo) + } + }) +} + +func ServePPROF(pprofPrefix string) http.Handler { + return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + endpoint := strings.TrimPrefix(request.URL.Path, pprofPrefix) + switch endpoint { + case "": + http.HandlerFunc(pprof.Index).ServeHTTP(writer, request) + case "cmdline": + http.HandlerFunc(pprof.Cmdline).ServeHTTP(writer, request) + case "profile": + http.HandlerFunc(pprof.Profile).ServeHTTP(writer, request) + case "symbol": + http.HandlerFunc(pprof.Symbol).ServeHTTP(writer, request) + case "trace": + http.HandlerFunc(pprof.Trace).ServeHTTP(writer, request) + case "allocs", "block", "goroutine", "heap", "mutex", "threadcreate": + pprof.Handler(endpoint).ServeHTTP(writer, request) + default: + writer.WriteHeader(http.StatusNotFound) + } + }) +} diff --git a/utils/httputil/formats.go b/utils/httputil/formats.go new file mode 100644 index 00000000..869e8dc9 --- /dev/null +++ b/utils/httputil/formats.go @@ -0,0 +1,22 @@ +package httputil + +import ( + "strings" + "time" +) + +const ( + // DateHeaderTimestampFormat - Last-Modified: , :: GMT + DateHeaderTimestampFormat = "Mon, 02 Jan 2006 15:04:05 GMT" +) + +func HeaderTimestamp(ts time.Time) string { + return ts.UTC().Format(DateHeaderTimestampFormat) +} + +func ETag(checksum string) string { + if strings.HasPrefix(checksum, `"`) { + return checksum + } + return `"` + checksum + `"` +} diff --git a/utils/httputil/metrics.go b/utils/httputil/metrics.go new file mode 100644 index 00000000..915d02b5 --- /dev/null +++ b/utils/httputil/metrics.go @@ -0,0 +1,17 @@ +package httputil + +import "net/http" + +type MetricResponseWriter struct { + http.ResponseWriter + StatusCode int +} + +func NewMetricResponseWriter(w http.ResponseWriter) *MetricResponseWriter { + return &MetricResponseWriter{ResponseWriter: w, StatusCode: http.StatusOK} +} + +func (mrw *MetricResponseWriter) WriteHeader(code int) { + mrw.StatusCode = code + mrw.ResponseWriter.WriteHeader(code) +} diff --git a/utils/httputil/range.go b/utils/httputil/range.go new file mode 100644 index 00000000..e4939439 --- /dev/null +++ b/utils/httputil/range.go @@ -0,0 +1,91 @@ +package httputil + +import ( + "errors" + "fmt" + "strconv" + "strings" +) + +var ErrBadRange = errors.New("invalid range") +var ErrUnsatisfiableRange = errors.New("unsatisfiable range") + +// Range represents an RFC 2616 HTTP Range +type Range struct { + StartOffset int64 + EndOffset int64 +} + +func (r Range) String() string { + return fmt.Sprintf("start=%d, end=%d (total=%d)", r.StartOffset, r.EndOffset, r.EndOffset-r.StartOffset+1) +} + +func (r Range) Size() int64 { + return r.EndOffset - r.StartOffset + 1 +} + +// ParseRange parses an HTTP RFC 2616 Range header value and returns an Range object for the given object length +func ParseRange(spec string, length int64) (Range, error) { + // Amazon S3 doesn't support retrieving multiple ranges of data per GET request. + var r Range + if !strings.HasPrefix(spec, "bytes=") { + return r, ErrBadRange + } + spec = strings.TrimPrefix(spec, "bytes=") + parts := strings.Split(spec, "-") + const rangeParts = 2 + if len(parts) != rangeParts { + return r, ErrBadRange + } + + fromString := parts[0] + toString := parts[1] + if len(fromString) == 0 && len(toString) == 0 { + return r, ErrBadRange + } + // negative only + if len(fromString) == 0 { + endOffset, err := strconv.ParseInt(toString, 10, 64) //nolint: gomnd + if err != nil { + return r, ErrBadRange + } + r.StartOffset = length - endOffset + if length-endOffset < 0 { + r.StartOffset = 0 + } + r.EndOffset = length - 1 + return r, nil + } + // positive only + if len(toString) == 0 { + beginOffset, err := strconv.ParseInt(fromString, 10, 64) //nolint: gomnd + if err != nil { + return r, ErrBadRange + } else if beginOffset > length-1 { + return r, ErrUnsatisfiableRange + } + r.StartOffset = beginOffset + r.EndOffset = length - 1 + return r, nil + } + // both set + beginOffset, err := strconv.ParseInt(fromString, 10, 64) //nolint: gomnd + if err != nil { + return r, ErrBadRange + } + endOffset, err := strconv.ParseInt(toString, 10, 64) //nolint: gomnd + if err != nil { + return r, ErrBadRange + } + // if endOffset exceeds length return length : this is how it works in s3 (presto for example uses range with a huge endOffset regardless to the file size) + if endOffset > length-1 { + endOffset = length - 1 + } + // if the beginning offset is after the size, it is unsatisfiable + if beginOffset > length-1 || endOffset > length-1 { + return r, ErrUnsatisfiableRange + } + r.StartOffset = beginOffset + r.EndOffset = endOffset + return r, nil +} diff --git a/utils/httputil/range_test.go b/utils/httputil/range_test.go new file mode 100644 index 00000000..c19b71c6 --- /dev/null +++ b/utils/httputil/range_test.go @@ -0,0 +1,60 @@ +package httputil_test + +import ( + "errors" + "fmt" + "testing" + + "github.com/jiaozifs/jiaozifs/utils/httputil" +) + +func TestParseRange(t *testing.T) { + cases := []struct { + Spec string + Length int + ExpectedError error + ExpectedStart int + ExpectedEnd int + ExpectedSize int + }{ + {"bytes=0-20", 50, nil, 0, 20, 21}, + {"bytes=0-20", 10, nil, 0, 9, 10}, + {"bytes=-20", 50, nil, 30, 49, 20}, + {"bytes=20-", 50, nil, 20, 49, 30}, + {"bytes=-20", 10, nil, 0, 9, 10}, + {"bytes=0-20", 20, nil, 0, 19, 20}, + {"bytes=0-19", 20, nil, 0, 19, 20}, + {"bytes=1-300", 20, nil, 1, 19, 19}, + {"bytes=-0-19", 20, httputil.ErrBadRange, 0, 0, 0}, + {"bytess=0-19", 20, httputil.ErrBadRange, 0, 0, 0}, + {"0-19", 20, httputil.ErrBadRange, 0, 0, 0}, + {"bytes=-", 20, httputil.ErrBadRange, 0, 0, 0}, + {"bytes=0-foo", 20, httputil.ErrBadRange, 0, 0, 0}, + {"bytes=foo-19", 20, httputil.ErrBadRange, 0, 0, 0}, + {"bytes=20-", 20, httputil.ErrUnsatisfiableRange, 0, 0, 0}, + {"bytes=21-", 20, httputil.ErrUnsatisfiableRange, 0, 0, 0}, + {"bytes=19-", 20, nil, 19, 19, 1}, + } + + for _, c := range cases { + t.Run(fmt.Sprintf("%s_length_%d", c.Spec, c.Length), func(t *testing.T) { + r, err := httputil.ParseRange(c.Spec, int64(c.Length)) + if !errors.Is(err, c.ExpectedError) { + t.Fatalf("epxected error: %v, got %v!", c.ExpectedError, err) + } + if err != nil { + return + } + + if r.EndOffset != int64(c.ExpectedEnd) { + t.Fatalf("expected end offset: %d, got %d", c.ExpectedEnd, r.EndOffset) + } + if r.StartOffset != int64(c.ExpectedStart) { + t.Fatalf("expected start offset: %d, got %d", c.ExpectedStart, r.StartOffset) + } + if r.Size() != int64(c.ExpectedSize) { + t.Fatalf("expected size: %d, got %d", c.ExpectedSize, r.Size()) + } + }) + } +} diff --git a/utils/httputil/request.go b/utils/httputil/request.go new file mode 100644 index 00000000..1f1cf70f --- /dev/null +++ b/utils/httputil/request.go @@ -0,0 +1,11 @@ +package httputil + +import ( + "context" + "errors" + "net/http" +) + +func IsRequestCanceled(r *http.Request) bool { + return errors.Is(r.Context().Err(), context.Canceled) +} diff --git a/utils/httputil/response.go b/utils/httputil/response.go new file mode 100644 index 00000000..0c89dceb --- /dev/null +++ b/utils/httputil/response.go @@ -0,0 +1,8 @@ +package httputil + +import "net/http" + +// IsSuccessStatusCode returns true for status code 2xx +func IsSuccessStatusCode(response *http.Response) bool { + return response.StatusCode >= http.StatusOK && response.StatusCode < http.StatusMultipleChoices +} diff --git a/utils/httputil/scheme.go b/utils/httputil/scheme.go new file mode 100644 index 00000000..e7a18770 --- /dev/null +++ b/utils/httputil/scheme.go @@ -0,0 +1,21 @@ +package httputil + +import "net/http" + +const ( + schemeHTTP = "http" + schemeHTTPS = "https" +) + +func RequestScheme(r *http.Request) string { + switch { + case r.URL.Scheme == schemeHTTPS: + return schemeHTTPS + case r.Header.Get("X-Forwarded-Proto") == schemeHTTPS: + return schemeHTTPS + case r.Header.Get("X-Forwarded-Ssl") == "on": + return schemeHTTPS + default: + return schemeHTTP + } +} diff --git a/utils/httputil/server.go b/utils/httputil/server.go new file mode 100644 index 00000000..55949724 --- /dev/null +++ b/utils/httputil/server.go @@ -0,0 +1,53 @@ +package httputil + +import ( + "net" + "net/http" + "strings" +) + +func HostOnly(hostname string) string { + if strings.Contains(hostname, ":") { + host, _, _ := net.SplitHostPort(hostname) + return host + } + return hostname +} + +func HostsOnly(hostname []string) []string { + ret := make([]string, len(hostname)) + for i := 0; i < len(hostname); i++ { + ret[i] = HostOnly(hostname[i]) + } + return ret +} + +func HostMatches(r *http.Request, hosts []string) bool { + host := HostOnly(r.Host) + vHost := HostsOnly(hosts) + for _, v := range vHost { + if v == host { + return true + } + } + return false +} + +func HostSubdomainOf(r *http.Request, hosts []string) bool { + host := HostOnly(r.Host) + subVHost := HostsOnly(hosts) + for i := 0; i < len(subVHost); i++ { + subVHost[i] = "." + subVHost[i] + } + for _, subV := range subVHost { + if !strings.HasSuffix(host, subV) || len(host) < len(subV)+1 { + continue + } + dot := strings.IndexRune(host, '.') + if dot > -1 && dot < len(host)-len(subV) { + continue + } + return true // it is a direct sub-domain + } + return false +} diff --git a/utils/httputil/server_test.go b/utils/httputil/server_test.go new file mode 100644 index 00000000..3221454f --- /dev/null +++ b/utils/httputil/server_test.go @@ -0,0 +1,83 @@ +package httputil + +import ( + "fmt" + "net/http" + "testing" +) + +func TestHostSubdomainOf(t *testing.T) { + type args struct { + v []string + } + tests := []struct { + name string + args args + host string + want bool + }{ + {name: "empty", args: args{v: []string{}}, host: "s3", want: false}, + {name: "short", args: args{v: []string{"s3.local.io"}}, host: "s3", want: false}, + {name: "short many", args: args{v: []string{"s3.local.io", "s3.dev.invalid"}}, host: "s3", want: false}, + {name: "extract", args: args{v: []string{"s3.local.io"}}, host: "s3.local.io", want: false}, + {name: "extract many", args: args{v: []string{"s3.dev.invalid", "s3.local.io"}}, host: "s3.local.io", want: false}, + {name: "no sub", args: args{v: []string{"s3.local.io"}}, host: ".s3.local.io", want: false}, + {name: "no sub many", args: args{v: []string{"s3.dev.invalid", "s3.local.io"}}, host: ".s3.local.io", want: false}, + {name: "dot sub", args: args{v: []string{"s3.local.io"}}, host: "..s3.local.io", want: false}, + {name: "dot sub many", args: args{v: []string{"s3.local.io", "s3.dev.invalid"}}, host: "..s3.local.io", want: false}, + {name: "invalid1", args: args{v: []string{"s3.local.io"}}, host: "1.asdfsa.s3.local.io", want: false}, + {name: "invalid1 many", args: args{v: []string{"s3.local.io", "s3.dev.invalid"}}, host: "1.asdfsa.s3.local.io", want: false}, + {name: "invalid2", args: args{v: []string{"s3.local.io"}}, host: ".asdfsa.s3.local.io", want: false}, + {name: "invalid2 many", args: args{v: []string{"s3.local.io", "s3.dev.invalid"}}, host: ".asdfsa.s3.local.io", want: false}, + {name: "subdomain", args: args{v: []string{"s3.local.io"}}, host: "asdfsa.s3.local.io", want: true}, + {name: "subdomain many", args: args{v: []string{"s3.dev.invalid", "s3.local.io", "s3.example.net"}}, host: "asdfsa.s3.local.io", want: true}, + {name: "subdomain port", args: args{v: []string{"s3.local.io:8000"}}, host: "sub.s3.local.io", want: true}, + {name: "subdomain port many", args: args{v: []string{"s3.dev.invalid:2000", "s3.local.io:8000", "s3.example.net:9000"}}, host: "sub.s3.local.io", want: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r, err := http.NewRequest("GET", fmt.Sprintf("https://%s/", tt.host), nil) + if err != nil { + t.Fatal(err) + } + got := HostSubdomainOf(r, tt.args.v) + if got != tt.want { + t.Errorf("HostSubdomainOf() '%s' test with '%s' got = %t, want = %t", tt.args.v, tt.host, got, tt.want) + } + }) + } +} + +func TestHostMatches(t *testing.T) { + type args struct { + v []string + } + tests := []struct { + name string + args args + host string + want bool + }{ + {name: "empty", args: args{v: []string{}}, host: "s3", want: false}, + {name: "short", args: args{v: []string{"s3.local.io"}}, host: "s3", want: false}, + {name: "short many", args: args{v: []string{"s3.local.io", "s3.dev.invalid"}}, host: "s3", want: false}, + {name: "extract", args: args{v: []string{"s3.local.io"}}, host: "s3.local.io", want: true}, + {name: "extract many", args: args{v: []string{"s3.dev.invalid", "s3.local.io", "s3.example.net"}}, host: "s3.local.io", want: true}, + {name: "subdomain", args: args{v: []string{"s3.local.io"}}, host: "sub.s3.local.io", want: false}, + {name: "subdomain many", args: args{v: []string{"s3.dev.invalid", "s3.local.io"}}, host: "sub.s3.local.io", want: false}, + {name: "empty", args: args{v: []string{"s3.local.io"}}, host: "", want: false}, + {name: "empty many", args: args{v: []string{"s3.dev.invalid", "s3.local.io"}}, host: "", want: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r, err := http.NewRequest("GET", fmt.Sprintf("https://%s/", tt.host), nil) + if err != nil { + t.Fatal(err) + } + got := HostMatches(r, tt.args.v) + if got != tt.want { + t.Errorf("HostMatches() '%s' test with '%s' got = %t, want = %t", tt.args.v, tt.host, got, tt.want) + } + }) + } +} diff --git a/utils/httputil/tracing.go b/utils/httputil/tracing.go new file mode 100644 index 00000000..57640999 --- /dev/null +++ b/utils/httputil/tracing.go @@ -0,0 +1,105 @@ +package httputil + +import ( + "io" + "net/http" +) + +const ( + MaxBodyBytes = 750 // Log lines will be < 2KiB + RequestTracingMaxRequestBodySize = 1024 * 1024 * 50 // 50KB + RequestTracingMaxResponseBodySize = 1024 * 1024 * 150 // 150KB +) + +type CappedBuffer struct { + SizeBytes int + cursor int + Buffer []byte +} + +func (c *CappedBuffer) Write(p []byte) (n int, err error) { + // pretend to write the whole thing, but only write SizeBytes + if c.cursor >= c.SizeBytes { + return len(p), nil + } + if c.Buffer == nil { + c.Buffer = make([]byte, 0) + } + var written int + if len(p) > (c.SizeBytes - c.cursor) { + c.Buffer = append(c.Buffer, p[0:(c.SizeBytes-c.cursor)]...) + written = c.SizeBytes - c.cursor + } else { + c.Buffer = append(c.Buffer, p...) + written = len(p) + } + c.cursor += written + return len(p), nil +} + +type responseTracingWriter struct { + StatusCode int + ResponseSize int64 + BodyRecorder *CappedBuffer + + Writer http.ResponseWriter + multiWriter io.Writer +} + +func newResponseTracingWriter(w http.ResponseWriter, sizeInBytes int) *responseTracingWriter { + buf := &CappedBuffer{ + SizeBytes: sizeInBytes, + } + mw := io.MultiWriter(w, buf) + return &responseTracingWriter{ + StatusCode: http.StatusOK, + BodyRecorder: buf, + Writer: w, + multiWriter: mw, + } +} + +func (w *responseTracingWriter) Header() http.Header { + return w.Writer.Header() +} + +func (w *responseTracingWriter) Write(data []byte) (int, error) { + return w.multiWriter.Write(data) +} + +func (w *responseTracingWriter) WriteHeader(statusCode int) { + w.StatusCode = statusCode + w.Writer.WriteHeader(statusCode) +} + +type requestBodyTracer struct { + body io.ReadCloser + bodyRecorder *CappedBuffer + tee io.Reader +} + +func newRequestBodyTracer(body io.ReadCloser, sizeInBytes int) *requestBodyTracer { + w := &CappedBuffer{ + SizeBytes: sizeInBytes, + } + return &requestBodyTracer{ + body: body, + bodyRecorder: w, + tee: io.TeeReader(body, w), + } +} + +func (r *requestBodyTracer) Read(p []byte) (n int, err error) { + return r.tee.Read(p) +} + +func (r *requestBodyTracer) Close() error { + return r.body.Close() +} + +func presentBody(body []byte) string { + if len(body) > MaxBodyBytes { + body = body[:MaxBodyBytes] + } + return string(body) +} diff --git a/utils/pathutil/hash_path.go b/utils/pathutil/hash_path.go new file mode 100644 index 00000000..1432fbde --- /dev/null +++ b/utils/pathutil/hash_path.go @@ -0,0 +1,12 @@ +package pathutil + +import ( + "path" + + "github.com/jiaozifs/jiaozifs/utils/hash" +) + +func PathOfHash(hash hash.Hash) string { + hex := hash.Hex() + return path.Join(hex[:2], hex[2:]) +} diff --git a/utils/pathutil/hash_path_test.go b/utils/pathutil/hash_path_test.go new file mode 100644 index 00000000..30c30d0a --- /dev/null +++ b/utils/pathutil/hash_path_test.go @@ -0,0 +1,15 @@ +package pathutil + +import ( + "encoding/hex" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/jiaozifs/jiaozifs/utils/hash" +) + +func TestPathOfHash(t *testing.T) { + hashBytes, _ := hex.DecodeString("7cfdd07889b3295d6a550914ab35e068") + require.Equal(t, "7c/fdd07889b3295d6a550914ab35e068", PathOfHash(hash.Hash(hashBytes))) +} From 443565875498841be1dc9991a1fd6aa7a3e222f0 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Sun, 3 Dec 2023 21:29:43 +0800 Subject: [PATCH 049/210] feat: add tree operation --- api/custom_response.go | 10 + api/jiaozifs.gen.go | 430 ++++++++++++++++++--------- api/swagger.yml | 17 -- auth/auth_test.go | 27 +- auth/basic_auth.go | 61 ++-- block/azure/adapter.go | 28 +- block/local/adapter.go | 6 +- block/mem/adapter.go | 3 +- block/s3/adapter.go | 10 +- controller/coremgr.go | 155 ---------- controller/object_ctl.go | 109 ++++--- controller/user_ctl.go | 48 +-- go.mod | 1 + go.sum | 23 ++ models/object.go | 204 ++++++++++--- models/object_test.go | 10 +- models/ref.go | 4 +- models/ref_test.go | 9 +- models/repository.go | 19 +- models/repository_test.go | 7 +- models/user.go | 65 ++-- models/user_test.go | 38 +-- models/wip.go | 16 +- testhelper/pg.go | 43 +++ utils/hash/hash.go | 7 + utils/hash/hashing_reader.go | 60 +++- utils/hash/hashing_reader_test.go | 47 +-- utils/httputil/tracing.go | 105 ------- versionmgr/tree.go | 475 ++++++++++++++++++++++++++++++ versionmgr/tree_test.go | 159 ++++++++++ 30 files changed, 1487 insertions(+), 709 deletions(-) delete mode 100644 controller/coremgr.go create mode 100644 testhelper/pg.go delete mode 100644 utils/httputil/tracing.go create mode 100644 versionmgr/tree.go create mode 100644 versionmgr/tree_test.go diff --git a/api/custom_response.go b/api/custom_response.go index 5c781d7f..cf3a5fac 100644 --- a/api/custom_response.go +++ b/api/custom_response.go @@ -19,11 +19,21 @@ func (response *JiaozifsResponse) JSON(v interface{}) { } } +func (response *JiaozifsResponse) OK() { + response.WriteHeader(http.StatusOK) +} + func (response *JiaozifsResponse) Error(err error) { response.WriteHeader(http.StatusInternalServerError) _, _ = response.Write([]byte(err.Error())) } +func (response *JiaozifsResponse) String(msg string) { + response.Header().Set("Content-Type", "text/plain;charset=UTF-8") + response.WriteHeader(http.StatusOK) + _, _ = response.Write([]byte(msg)) +} + func (response *JiaozifsResponse) CodeMsg(code int, msg string) { response.WriteHeader(code) _, _ = response.Write([]byte(msg)) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 43ac5ca8..90f3d725 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -28,12 +28,6 @@ const ( Jwt_tokenScopes = "jwt_token.Scopes" ) -// Defines values for ObjectStatsPathType. -const ( - CommonPrefix ObjectStatsPathType = "common_prefix" - Object ObjectStatsPathType = "object" -) - // AuthenticationToken defines model for AuthenticationToken. type AuthenticationToken struct { // Token a JWT token that could be used to authenticate requests @@ -52,28 +46,12 @@ type ObjectStats struct { Metadata *ObjectUserMetadata `json:"metadata,omitempty"` // Mtime Unix Epoch in seconds - Mtime int64 `json:"mtime"` - Path string `json:"path"` - PathType ObjectStatsPathType `json:"path_type"` - - // PhysicalAddress The location of the object on the underlying object store. - // Formatted as a native URI with the object store type as scheme ("s3://...", "gs://...", etc.) - // Or, in the case of presign=true, will be an HTTP URL to be consumed via regular HTTP GET - PhysicalAddress string `json:"physical_address"` - - // PhysicalAddressExpiry If present and nonzero, physical_address is a pre-signed URL and - // will expire at this Unix Epoch time. This will be shorter than - // the pre-signed URL lifetime if an authentication token is about - // to expire. - // - // This field is *optional*. - PhysicalAddressExpiry *int64 `json:"physical_address_expiry,omitempty"` - SizeBytes *int64 `json:"size_bytes,omitempty"` + Mtime int64 `json:"mtime"` + Path string `json:"path"` + PathMode *uint32 `json:"path_mode,omitempty"` + SizeBytes *int64 `json:"size_bytes,omitempty"` } -// ObjectStatsPathType defines model for ObjectStats.PathType. -type ObjectStatsPathType string - // ObjectUserMetadata defines model for ObjectUserMetadata. type ObjectUserMetadata map[string]string @@ -113,6 +91,9 @@ type LoginJSONBody struct { // DeleteObjectParams defines parameters for DeleteObject. type DeleteObjectParams struct { + // Branch branch to the ref + Branch string `form:"branch" json:"branch"` + // Path relative to the ref Path string `form:"path" json:"path"` } @@ -121,6 +102,9 @@ type DeleteObjectParams struct { type GetObjectParams struct { Presign *bool `form:"presign,omitempty" json:"presign,omitempty"` + // Branch branch to the ref + Branch string `form:"branch" json:"branch"` + // Path relative to the ref Path string `form:"path" json:"path"` @@ -130,6 +114,9 @@ type GetObjectParams struct { // HeadObjectParams defines parameters for HeadObject. type HeadObjectParams struct { + // Branch branch to the ref + Branch string `form:"branch" json:"branch"` + // Path relative to the ref Path string `form:"path" json:"path"` @@ -148,6 +135,9 @@ type UploadObjectParams struct { // StorageClass Deprecated, this capability will not be supported in future releases. StorageClass *string `form:"storageClass,omitempty" json:"storageClass,omitempty"` + // Branch branch to the ref + Branch string `form:"branch" json:"branch"` + // Path relative to the ref Path string `form:"path" json:"path"` @@ -252,16 +242,16 @@ type ClientInterface interface { GetUserInfo(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) // DeleteObject request - DeleteObject(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) + DeleteObject(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetObject request - GetObject(ctx context.Context, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) + GetObject(ctx context.Context, user string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) // HeadObject request - HeadObject(ctx context.Context, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) + HeadObject(ctx context.Context, user string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) // UploadObjectWithBody request with any body - UploadObjectWithBody(ctx context.Context, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + UploadObjectWithBody(ctx context.Context, user string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) // GetVersion request GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -327,8 +317,8 @@ func (c *Client) GetUserInfo(ctx context.Context, reqEditors ...RequestEditorFn) return c.Client.Do(req) } -func (c *Client) DeleteObject(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteObjectRequest(c.Server, repository, params) +func (c *Client) DeleteObject(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteObjectRequest(c.Server, user, repository, params) if err != nil { return nil, err } @@ -339,8 +329,8 @@ func (c *Client) DeleteObject(ctx context.Context, repository string, params *De return c.Client.Do(req) } -func (c *Client) GetObject(ctx context.Context, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetObjectRequest(c.Server, repository, params) +func (c *Client) GetObject(ctx context.Context, user string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetObjectRequest(c.Server, user, repository, params) if err != nil { return nil, err } @@ -351,8 +341,8 @@ func (c *Client) GetObject(ctx context.Context, repository string, params *GetOb return c.Client.Do(req) } -func (c *Client) HeadObject(ctx context.Context, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewHeadObjectRequest(c.Server, repository, params) +func (c *Client) HeadObject(ctx context.Context, user string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewHeadObjectRequest(c.Server, user, repository, params) if err != nil { return nil, err } @@ -363,8 +353,8 @@ func (c *Client) HeadObject(ctx context.Context, repository string, params *Head return c.Client.Do(req) } -func (c *Client) UploadObjectWithBody(ctx context.Context, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewUploadObjectRequestWithBody(c.Server, repository, params, contentType, body) +func (c *Client) UploadObjectWithBody(ctx context.Context, user string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUploadObjectRequestWithBody(c.Server, user, repository, params, contentType, body) if err != nil { return nil, err } @@ -495,12 +485,19 @@ func NewGetUserInfoRequest(server string) (*http.Request, error) { } // NewDeleteObjectRequest generates requests for DeleteObject -func NewDeleteObjectRequest(server string, repository string, params *DeleteObjectParams) (*http.Request, error) { +func NewDeleteObjectRequest(server string, user string, repository string, params *DeleteObjectParams) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) if err != nil { return nil, err } @@ -510,7 +507,7 @@ func NewDeleteObjectRequest(server string, repository string, params *DeleteObje return nil, err } - operationPath := fmt.Sprintf("/repositories/%s/objects", pathParam0) + operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -523,6 +520,18 @@ func NewDeleteObjectRequest(server string, repository string, params *DeleteObje if params != nil { queryValues := queryURL.Query() + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "branch", runtime.ParamLocationQuery, params.Branch); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { @@ -547,12 +556,19 @@ func NewDeleteObjectRequest(server string, repository string, params *DeleteObje } // NewGetObjectRequest generates requests for GetObject -func NewGetObjectRequest(server string, repository string, params *GetObjectParams) (*http.Request, error) { +func NewGetObjectRequest(server string, user string, repository string, params *GetObjectParams) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) if err != nil { return nil, err } @@ -562,7 +578,7 @@ func NewGetObjectRequest(server string, repository string, params *GetObjectPara return nil, err } - operationPath := fmt.Sprintf("/repositories/%s/objects", pathParam0) + operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -591,6 +607,18 @@ func NewGetObjectRequest(server string, repository string, params *GetObjectPara } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "branch", runtime.ParamLocationQuery, params.Branch); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { @@ -630,12 +658,19 @@ func NewGetObjectRequest(server string, repository string, params *GetObjectPara } // NewHeadObjectRequest generates requests for HeadObject -func NewHeadObjectRequest(server string, repository string, params *HeadObjectParams) (*http.Request, error) { +func NewHeadObjectRequest(server string, user string, repository string, params *HeadObjectParams) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) if err != nil { return nil, err } @@ -645,7 +680,7 @@ func NewHeadObjectRequest(server string, repository string, params *HeadObjectPa return nil, err } - operationPath := fmt.Sprintf("/repositories/%s/objects", pathParam0) + operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -658,6 +693,18 @@ func NewHeadObjectRequest(server string, repository string, params *HeadObjectPa if params != nil { queryValues := queryURL.Query() + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "branch", runtime.ParamLocationQuery, params.Branch); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { @@ -697,12 +744,19 @@ func NewHeadObjectRequest(server string, repository string, params *HeadObjectPa } // NewUploadObjectRequestWithBody generates requests for UploadObject with any type of body -func NewUploadObjectRequestWithBody(server string, repository string, params *UploadObjectParams, contentType string, body io.Reader) (*http.Request, error) { +func NewUploadObjectRequestWithBody(server string, user string, repository string, params *UploadObjectParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) if err != nil { return nil, err } @@ -712,7 +766,7 @@ func NewUploadObjectRequestWithBody(server string, repository string, params *Up return nil, err } - operationPath := fmt.Sprintf("/repositories/%s/objects", pathParam0) + operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -741,6 +795,18 @@ func NewUploadObjectRequestWithBody(server string, repository string, params *Up } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "branch", runtime.ParamLocationQuery, params.Branch); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { @@ -865,16 +931,16 @@ type ClientWithResponsesInterface interface { GetUserInfoWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetUserInfoResponse, error) // DeleteObjectWithResponse request - DeleteObjectWithResponse(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) + DeleteObjectWithResponse(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) // GetObjectWithResponse request - GetObjectWithResponse(ctx context.Context, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*GetObjectResponse, error) + GetObjectWithResponse(ctx context.Context, user string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*GetObjectResponse, error) // HeadObjectWithResponse request - HeadObjectWithResponse(ctx context.Context, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*HeadObjectResponse, error) + HeadObjectWithResponse(ctx context.Context, user string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*HeadObjectResponse, error) // UploadObjectWithBodyWithResponse request with any body - UploadObjectWithBodyWithResponse(ctx context.Context, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) + UploadObjectWithBodyWithResponse(ctx context.Context, user string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) // GetVersionWithResponse request GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) @@ -1096,8 +1162,8 @@ func (c *ClientWithResponses) GetUserInfoWithResponse(ctx context.Context, reqEd } // DeleteObjectWithResponse request returning *DeleteObjectResponse -func (c *ClientWithResponses) DeleteObjectWithResponse(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) { - rsp, err := c.DeleteObject(ctx, repository, params, reqEditors...) +func (c *ClientWithResponses) DeleteObjectWithResponse(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) { + rsp, err := c.DeleteObject(ctx, user, repository, params, reqEditors...) if err != nil { return nil, err } @@ -1105,8 +1171,8 @@ func (c *ClientWithResponses) DeleteObjectWithResponse(ctx context.Context, repo } // GetObjectWithResponse request returning *GetObjectResponse -func (c *ClientWithResponses) GetObjectWithResponse(ctx context.Context, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*GetObjectResponse, error) { - rsp, err := c.GetObject(ctx, repository, params, reqEditors...) +func (c *ClientWithResponses) GetObjectWithResponse(ctx context.Context, user string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*GetObjectResponse, error) { + rsp, err := c.GetObject(ctx, user, repository, params, reqEditors...) if err != nil { return nil, err } @@ -1114,8 +1180,8 @@ func (c *ClientWithResponses) GetObjectWithResponse(ctx context.Context, reposit } // HeadObjectWithResponse request returning *HeadObjectResponse -func (c *ClientWithResponses) HeadObjectWithResponse(ctx context.Context, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*HeadObjectResponse, error) { - rsp, err := c.HeadObject(ctx, repository, params, reqEditors...) +func (c *ClientWithResponses) HeadObjectWithResponse(ctx context.Context, user string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*HeadObjectResponse, error) { + rsp, err := c.HeadObject(ctx, user, repository, params, reqEditors...) if err != nil { return nil, err } @@ -1123,8 +1189,8 @@ func (c *ClientWithResponses) HeadObjectWithResponse(ctx context.Context, reposi } // UploadObjectWithBodyWithResponse request with arbitrary body returning *UploadObjectResponse -func (c *ClientWithResponses) UploadObjectWithBodyWithResponse(ctx context.Context, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) { - rsp, err := c.UploadObjectWithBody(ctx, repository, params, contentType, body, reqEditors...) +func (c *ClientWithResponses) UploadObjectWithBodyWithResponse(ctx context.Context, user string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) { + rsp, err := c.UploadObjectWithBody(ctx, user, repository, params, contentType, body, reqEditors...) if err != nil { return nil, err } @@ -1312,28 +1378,28 @@ func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) { type ServerInterface interface { // perform a login // (POST /auth/login) - Login(w *JiaozifsResponse, r *http.Request) + Login(ctx context.Context, w *JiaozifsResponse, r *http.Request) // perform user registration // (POST /auth/register) - Register(w *JiaozifsResponse, r *http.Request) + Register(ctx context.Context, w *JiaozifsResponse, r *http.Request) // get information of the currently logged-in user // (GET /auth/user) - GetUserInfo(w *JiaozifsResponse, r *http.Request) + GetUserInfo(ctx context.Context, w *JiaozifsResponse, r *http.Request) // delete object. Missing objects will not return a NotFound error. - // (DELETE /repositories/{repository}/objects) - DeleteObject(w *JiaozifsResponse, r *http.Request, repository string, params DeleteObjectParams) + // (DELETE /object/{user}/{repository}) + DeleteObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params DeleteObjectParams) // get object content - // (GET /repositories/{repository}/objects) - GetObject(w *JiaozifsResponse, r *http.Request, repository string, params GetObjectParams) + // (GET /object/{user}/{repository}) + GetObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params GetObjectParams) // check if object exists - // (HEAD /repositories/{repository}/objects) - HeadObject(w *JiaozifsResponse, r *http.Request, repository string, params HeadObjectParams) + // (HEAD /object/{user}/{repository}) + HeadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params HeadObjectParams) - // (POST /repositories/{repository}/objects) - UploadObject(w *JiaozifsResponse, r *http.Request, repository string, params UploadObjectParams) + // (POST /object/{user}/{repository}) + UploadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params UploadObjectParams) // return program and runtime version // (GET /version) - GetVersion(w *JiaozifsResponse, r *http.Request) + GetVersion(ctx context.Context, w *JiaozifsResponse, r *http.Request) } // Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. @@ -1342,48 +1408,48 @@ type Unimplemented struct{} // perform a login // (POST /auth/login) -func (_ Unimplemented) Login(w *JiaozifsResponse, r *http.Request) { +func (_ Unimplemented) Login(ctx context.Context, w *JiaozifsResponse, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } // perform user registration // (POST /auth/register) -func (_ Unimplemented) Register(w *JiaozifsResponse, r *http.Request) { +func (_ Unimplemented) Register(ctx context.Context, w *JiaozifsResponse, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } // get information of the currently logged-in user // (GET /auth/user) -func (_ Unimplemented) GetUserInfo(w *JiaozifsResponse, r *http.Request) { +func (_ Unimplemented) GetUserInfo(ctx context.Context, w *JiaozifsResponse, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } // delete object. Missing objects will not return a NotFound error. -// (DELETE /repositories/{repository}/objects) -func (_ Unimplemented) DeleteObject(w *JiaozifsResponse, r *http.Request, repository string, params DeleteObjectParams) { +// (DELETE /object/{user}/{repository}) +func (_ Unimplemented) DeleteObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params DeleteObjectParams) { w.WriteHeader(http.StatusNotImplemented) } // get object content -// (GET /repositories/{repository}/objects) -func (_ Unimplemented) GetObject(w *JiaozifsResponse, r *http.Request, repository string, params GetObjectParams) { +// (GET /object/{user}/{repository}) +func (_ Unimplemented) GetObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params GetObjectParams) { w.WriteHeader(http.StatusNotImplemented) } // check if object exists -// (HEAD /repositories/{repository}/objects) -func (_ Unimplemented) HeadObject(w *JiaozifsResponse, r *http.Request, repository string, params HeadObjectParams) { +// (HEAD /object/{user}/{repository}) +func (_ Unimplemented) HeadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params HeadObjectParams) { w.WriteHeader(http.StatusNotImplemented) } -// (POST /repositories/{repository}/objects) -func (_ Unimplemented) UploadObject(w *JiaozifsResponse, r *http.Request, repository string, params UploadObjectParams) { +// (POST /object/{user}/{repository}) +func (_ Unimplemented) UploadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params UploadObjectParams) { w.WriteHeader(http.StatusNotImplemented) } // return program and runtime version // (GET /version) -func (_ Unimplemented) GetVersion(w *JiaozifsResponse, r *http.Request) { +func (_ Unimplemented) GetVersion(ctx context.Context, w *JiaozifsResponse, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } @@ -1401,7 +1467,7 @@ func (siw *ServerInterfaceWrapper) Login(w http.ResponseWriter, r *http.Request) ctx := r.Context() handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.Login(&JiaozifsResponse{w}, r) + siw.Handler.Login(r.Context(), &JiaozifsResponse{w}, r) })) for _, middleware := range siw.HandlerMiddlewares { @@ -1416,7 +1482,7 @@ func (siw *ServerInterfaceWrapper) Register(w http.ResponseWriter, r *http.Reque ctx := r.Context() handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.Register(&JiaozifsResponse{w}, r) + siw.Handler.Register(r.Context(), &JiaozifsResponse{w}, r) })) for _, middleware := range siw.HandlerMiddlewares { @@ -1433,7 +1499,7 @@ func (siw *ServerInterfaceWrapper) GetUserInfo(w http.ResponseWriter, r *http.Re ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetUserInfo(&JiaozifsResponse{w}, r) + siw.Handler.GetUserInfo(r.Context(), &JiaozifsResponse{w}, r) })) for _, middleware := range siw.HandlerMiddlewares { @@ -1449,6 +1515,15 @@ func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.R var err error + // ------------- Path parameter "user" ------------- + var user string + + err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + return + } + // ------------- Path parameter "repository" ------------- var repository string @@ -1465,6 +1540,21 @@ func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.R // Parameter object where we will unmarshal all parameters from the context var params DeleteObjectParams + // ------------- Required query parameter "branch" ------------- + + if paramValue := r.URL.Query().Get("branch"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "branch"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "branch", r.URL.Query(), ¶ms.Branch) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "branch", Err: err}) + return + } + // ------------- Required query parameter "path" ------------- if paramValue := r.URL.Query().Get("path"); paramValue != "" { @@ -1481,7 +1571,7 @@ func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.R } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.DeleteObject(&JiaozifsResponse{w}, r, repository, params) + siw.Handler.DeleteObject(r.Context(), &JiaozifsResponse{w}, r, user, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -1497,6 +1587,15 @@ func (siw *ServerInterfaceWrapper) GetObject(w http.ResponseWriter, r *http.Requ var err error + // ------------- Path parameter "user" ------------- + var user string + + err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + return + } + // ------------- Path parameter "repository" ------------- var repository string @@ -1521,6 +1620,21 @@ func (siw *ServerInterfaceWrapper) GetObject(w http.ResponseWriter, r *http.Requ return } + // ------------- Required query parameter "branch" ------------- + + if paramValue := r.URL.Query().Get("branch"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "branch"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "branch", r.URL.Query(), ¶ms.Branch) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "branch", Err: err}) + return + } + // ------------- Required query parameter "path" ------------- if paramValue := r.URL.Query().Get("path"); paramValue != "" { @@ -1558,7 +1672,7 @@ func (siw *ServerInterfaceWrapper) GetObject(w http.ResponseWriter, r *http.Requ } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetObject(&JiaozifsResponse{w}, r, repository, params) + siw.Handler.GetObject(r.Context(), &JiaozifsResponse{w}, r, user, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -1574,6 +1688,15 @@ func (siw *ServerInterfaceWrapper) HeadObject(w http.ResponseWriter, r *http.Req var err error + // ------------- Path parameter "user" ------------- + var user string + + err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + return + } + // ------------- Path parameter "repository" ------------- var repository string @@ -1590,6 +1713,21 @@ func (siw *ServerInterfaceWrapper) HeadObject(w http.ResponseWriter, r *http.Req // Parameter object where we will unmarshal all parameters from the context var params HeadObjectParams + // ------------- Required query parameter "branch" ------------- + + if paramValue := r.URL.Query().Get("branch"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "branch"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "branch", r.URL.Query(), ¶ms.Branch) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "branch", Err: err}) + return + } + // ------------- Required query parameter "path" ------------- if paramValue := r.URL.Query().Get("path"); paramValue != "" { @@ -1627,7 +1765,7 @@ func (siw *ServerInterfaceWrapper) HeadObject(w http.ResponseWriter, r *http.Req } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.HeadObject(&JiaozifsResponse{w}, r, repository, params) + siw.Handler.HeadObject(r.Context(), &JiaozifsResponse{w}, r, user, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -1643,6 +1781,15 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R var err error + // ------------- Path parameter "user" ------------- + var user string + + err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + return + } + // ------------- Path parameter "repository" ------------- var repository string @@ -1667,6 +1814,21 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R return } + // ------------- Required query parameter "branch" ------------- + + if paramValue := r.URL.Query().Get("branch"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "branch"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "branch", r.URL.Query(), ¶ms.Branch) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "branch", Err: err}) + return + } + // ------------- Required query parameter "path" ------------- if paramValue := r.URL.Query().Get("path"); paramValue != "" { @@ -1704,7 +1866,7 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.UploadObject(&JiaozifsResponse{w}, r, repository, params) + siw.Handler.UploadObject(r.Context(), &JiaozifsResponse{w}, r, user, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -1721,7 +1883,7 @@ func (siw *ServerInterfaceWrapper) GetVersion(w http.ResponseWriter, r *http.Req ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetVersion(&JiaozifsResponse{w}, r) + siw.Handler.GetVersion(r.Context(), &JiaozifsResponse{w}, r) })) for _, middleware := range siw.HandlerMiddlewares { @@ -1854,16 +2016,16 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Get(options.BaseURL+"/auth/user", wrapper.GetUserInfo) }) r.Group(func(r chi.Router) { - r.Delete(options.BaseURL+"/repositories/{repository}/objects", wrapper.DeleteObject) + r.Delete(options.BaseURL+"/object/{user}/{repository}", wrapper.DeleteObject) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/repositories/{repository}/objects", wrapper.GetObject) + r.Get(options.BaseURL+"/object/{user}/{repository}", wrapper.GetObject) }) r.Group(func(r chi.Router) { - r.Head(options.BaseURL+"/repositories/{repository}/objects", wrapper.HeadObject) + r.Head(options.BaseURL+"/object/{user}/{repository}", wrapper.HeadObject) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/repositories/{repository}/objects", wrapper.UploadObject) + r.Post(options.BaseURL+"/object/{user}/{repository}", wrapper.UploadObject) }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/version", wrapper.GetVersion) @@ -1875,44 +2037,40 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "2WK0+HcAAAD//87c0PRRIwAA", + "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", + "73qPL0aLfwUAAP//iqm+sXAhAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 8f929dc2..21c84163 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -132,7 +132,6 @@ components: type: object required: - checksum - - physical_address - path - path_type - mtime @@ -142,22 +141,6 @@ components: path_mode: type: integer format: uint32 - physical_address: - type: string - description: | - The location of the object on the underlying object store. - Formatted as a native URI with the object store type as scheme ("s3://...", "gs://...", etc.) - Or, in the case of presign=true, will be an HTTP URL to be consumed via regular HTTP GET - physical_address_expiry: - type: integer - format: int64 - description: | - If present and nonzero, physical_address is a pre-signed URL and - will expire at this Unix Epoch time. This will be shorter than - the pre-signed URL lifetime if an authentication token is about - to expire. - - This field is *optional*. checksum: type: string size_bytes: diff --git a/auth/auth_test.go b/auth/auth_test.go index 63b0d708..f1b1eb74 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -2,40 +2,19 @@ package auth import ( "context" - "fmt" "testing" + "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/brianvoe/gofakeit/v6" - embeddedpostgres "github.com/fergusstrange/embedded-postgres" "github.com/jiaozifs/jiaozifs/config" "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/models/migrations" - "github.com/phayes/freeport" "github.com/stretchr/testify/require" - "github.com/uptrace/bun" - "go.uber.org/fx/fxtest" ) -var testConnTmpl = "postgres://postgres:postgres@localhost:%d/jiaozifs?sslmode=disable" - -func setup(ctx context.Context, t *testing.T) (*embeddedpostgres.EmbeddedPostgres, *bun.DB) { - port, err := freeport.GetFreePort() - require.NoError(t, err) - postgres := embeddedpostgres.NewDatabase(embeddedpostgres.DefaultConfig().Port(uint32(port)).Database("jiaozifs")) - err = postgres.Start() - require.NoError(t, err) - - db, err := models.SetupDatabase(ctx, fxtest.NewLifecycle(t), &config.DatabaseConfig{Debug: true, Connection: fmt.Sprintf(testConnTmpl, port)}) - require.NoError(t, err) - - err = migrations.MigrateDatabase(ctx, db) - require.NoError(t, err) - return postgres, db -} - func TestLogin_Success(t *testing.T) { ctx := context.Background() - postgres, db := setup(ctx, t) + postgres, _, db := testhelper.SetupDatabase(ctx, t) defer postgres.Stop() //nolint // repo mockRepo := models.NewUserRepo(db) diff --git a/auth/basic_auth.go b/auth/basic_auth.go index d69d3678..dbdca4cc 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -2,12 +2,15 @@ package auth import ( "context" + "fmt" "time" + "github.com/jiaozifs/jiaozifs/utils" + "github.com/jiaozifs/jiaozifs/config" "github.com/golang-jwt/jwt" - openapi_types "github.com/oapi-codegen/runtime/types" + openapitypes "github.com/oapi-codegen/runtime/types" "github.com/go-openapi/swag" logging "github.com/ipfs/go-log/v2" @@ -24,18 +27,17 @@ type Login struct { } func (l *Login) Login(ctx context.Context, repo models.IUserRepo, config *config.Config) (token api.AuthenticationToken, err error) { - // Get user encryptedPassword by username + // get user encryptedPassword by username ep, err := repo.GetEPByName(ctx, l.Username) if err != nil { - log.Errorf("username err: %s", err) - return token, err + return token, fmt.Errorf("cannt get user %s encrypt password %w", l.Username, err) } // Compare ep and password err = bcrypt.CompareHashAndPassword([]byte(ep), []byte(l.Password)) if err != nil { log.Errorf("password err: %s", err) - return token, err + return token, fmt.Errorf("user %s password not match %w", l.Username, err) } // Generate user token loginTime := time.Now() @@ -44,15 +46,13 @@ func (l *Login) Login(ctx context.Context, repo models.IUserRepo, config *config tokenString, err := GenerateJWTLogin(secretKey, l.Username, loginTime, expires) if err != nil { - log.Errorf("generate token err: %s", err) - return token, err + return token, fmt.Errorf("generate token err: %w", err) } - log.Info("login successful") + log.Infof("usert %s login successful", l.Username) token.Token = tokenString token.TokenExpiration = swag.Int64(expires.Unix()) - return token, nil } @@ -62,20 +62,25 @@ type Register struct { Password string `json:"password"` } -func (r *Register) Register(ctx context.Context, repo models.IUserRepo) (err error) { +func (r *Register) Register(ctx context.Context, repo models.IUserRepo) error { // check username, email - _, err1 := repo.GetUserByName(ctx, r.Username) - _, err2 := repo.GetUserByEmail(ctx, r.Email) - if err1 == nil || err2 == nil { - err = ErrInvalidNameEmail - log.Error(ErrInvalidNameEmail) - return + count1, err := repo.Count(ctx, &models.CountUserParams{Name: utils.String(r.Username)}) + if err != nil { + return err + } + count2, err := repo.Count(ctx, &models.CountUserParams{Name: utils.String(r.Email)}) + if err != nil { + return err } + + if count1+count2 > 0 { + return fmt.Errorf("username %s or email %s not found %w ", r.Username, r.Email, ErrInvalidNameEmail) + } + // reserve temporarily password, err := bcrypt.GenerateFromPassword([]byte(r.Password), passwordCost) if err != nil { - log.Error(ErrComparePassword) - return + return fmt.Errorf("invalid password %w", err) } // insert db @@ -88,14 +93,13 @@ func (r *Register) Register(ctx context.Context, repo models.IUserRepo) (err err CurrentSignInIP: "", LastSignInIP: "", CreatedAt: time.Now(), - UpdatedAt: time.Time{}, + UpdatedAt: time.Now(), } insertUser, err := repo.Insert(ctx, user) if err != nil { - log.Error("create user error") - return + return fmt.Errorf("inser user %s user error %w", r.Username, err) } - // return + log.Infof("%s registration success", insertUser.Name) return nil } @@ -111,24 +115,21 @@ func (u *UserInfo) UserProfile(ctx context.Context, repo models.IUserRepo, confi return config.Auth.SecretKey, nil }) if err != nil { - log.Error(ErrParseToken) - return userInfo, err + return userInfo, fmt.Errorf("cannot parse token %s %w", token.Raw, err) } - // Check Token validity + // check token validity if !token.Valid { - log.Error(ErrInvalidToken) - return userInfo, ErrInvalidToken + return userInfo, fmt.Errorf("token %s invalid %w", token.Raw, ErrInvalidToken) } // Get username by token claims, ok := token.Claims.(jwt.MapClaims) if !ok { - log.Error(ErrExtractClaims) return userInfo, ErrExtractClaims } username := claims["sub"].(string) // Get user by username - user, err := repo.GetUserByName(ctx, username) + user, err := repo.Get(ctx, &models.GetUserParam{Name: utils.String(username)}) if err != nil { return userInfo, err } @@ -136,7 +137,7 @@ func (u *UserInfo) UserProfile(ctx context.Context, repo models.IUserRepo, confi CreatedAt: &user.CreatedAt, CurrentSignInAt: &user.CurrentSignInAt, CurrentSignInIP: &user.CurrentSignInIP, - Email: openapi_types.Email(user.Email), + Email: openapitypes.Email(user.Email), LastSignInAt: &user.LastSignInAt, LastSignInIP: &user.LastSignInIP, UpdateAt: &user.UpdatedAt, diff --git a/block/azure/adapter.go b/block/azure/adapter.go index 409344fa..c522fa9c 100644 --- a/block/azure/adapter.go +++ b/block/azure/adapter.go @@ -141,40 +141,18 @@ func resolveBlobURLInfo(obj block.ObjectPointer) (BlobURLInfo, error) { return ResolveBlobURLInfoFromURL(parsedKey) } -func (a *Adapter) translatePutOpts(_ context.Context, opts block.PutOpts) azblob.UploadStreamOptions { - res := azblob.UploadStreamOptions{} - if opts.StorageClass == nil { - return res - } - - for _, t := range blob.PossibleAccessTierValues() { - if strings.EqualFold(*opts.StorageClass, string(t)) { - accessTier := t - res.AccessTier = &accessTier - break - } - } - - if res.AccessTier == nil { - log.With("tier_type", *opts.StorageClass).Warn("Unknown Azure tier type") - } - - return res -} - -func (a *Adapter) Put(ctx context.Context, obj block.ObjectPointer, sizeBytes int64, reader io.Reader, opts block.PutOpts) error { +func (a *Adapter) Put(ctx context.Context, obj block.ObjectPointer, sizeBytes int64, reader io.Reader, _ block.PutOpts) error { var err error defer reportMetrics("Put", time.Now(), &sizeBytes, &err) qualifiedKey, err := resolveBlobURLInfo(obj) if err != nil { return err } - o := a.translatePutOpts(ctx, opts) containerClient, err := a.clientCache.NewContainerClient(qualifiedKey.StorageAccountName, qualifiedKey.ContainerName) if err != nil { return err } - _, err = containerClient.NewBlockBlobClient(qualifiedKey.BlobURL).UploadStream(ctx, reader, &o) + _, err = containerClient.NewBlockBlobClient(qualifiedKey.BlobURL).UploadStream(ctx, reader, &azblob.UploadStreamOptions{}) return err } @@ -491,7 +469,7 @@ func (a *Adapter) UploadPart(ctx context.Context, obj block.ObjectPointer, _ int if err != nil { return nil, err } - hashReader := hash.NewHashingReader(reader, hash.HashFunctionMD5) + hashReader := hash.NewHashingReader(reader, hash.Md5) multipartBlockWriter := NewMultipartBlockWriter(hashReader, *container, qualifiedKey.BlobURL) _, err = copyFromReader(ctx, hashReader, multipartBlockWriter, blockblob.UploadStreamOptions{ diff --git a/block/local/adapter.go b/block/local/adapter.go index 0179fa79..71589dce 100644 --- a/block/local/adapter.go +++ b/block/local/adapter.go @@ -243,7 +243,7 @@ func (l *Adapter) UploadCopyPart(ctx context.Context, sourceObj, destinationObj if err != nil { return nil, err } - md5Read := hash.NewHashingReader(r, hash.HashFunctionMD5) + md5Read := hash.NewHashingReader(r, hash.Md5) fName := uploadID + fmt.Sprintf("-%05d", partNumber) err = l.Put(ctx, block.ObjectPointer{StorageNamespace: destinationObj.StorageNamespace, Identifier: fName}, -1, md5Read, block.PutOpts{}) if err != nil { @@ -263,7 +263,7 @@ func (l *Adapter) UploadCopyPartRange(ctx context.Context, sourceObj, destinatio if err != nil { return nil, err } - md5Read := hash.NewHashingReader(r, hash.HashFunctionMD5) + md5Read := hash.NewHashingReader(r, hash.Md5) fName := uploadID + fmt.Sprintf("-%05d", partNumber) err = l.Put(ctx, block.ObjectPointer{StorageNamespace: destinationObj.StorageNamespace, Identifier: fName}, -1, md5Read, block.PutOpts{}) if err != nil { @@ -395,7 +395,7 @@ func (l *Adapter) UploadPart(ctx context.Context, obj block.ObjectPointer, _ int if err := isValidUploadID(uploadID); err != nil { return nil, err } - md5Read := hash.NewHashingReader(reader, hash.HashFunctionMD5) + md5Read := hash.NewHashingReader(reader, hash.Md5) fName := uploadID + fmt.Sprintf("-%05d", partNumber) err := l.Put(ctx, block.ObjectPointer{StorageNamespace: obj.StorageNamespace, Identifier: fName}, -1, md5Read, block.PutOpts{}) etag := hex.EncodeToString(md5Read.Md5.Sum(nil)) diff --git a/block/mem/adapter.go b/block/mem/adapter.go index 240c2b48..e7028864 100644 --- a/block/mem/adapter.go +++ b/block/mem/adapter.go @@ -78,7 +78,7 @@ func getKey(obj block.ObjectPointer) string { return fmt.Sprintf("%s:%s", obj.StorageNamespace, obj.Identifier) } -func (a *Adapter) Put(_ context.Context, obj block.ObjectPointer, _ int64, reader io.Reader, opts block.PutOpts) error { +func (a *Adapter) Put(_ context.Context, obj block.ObjectPointer, _ int64, reader io.Reader, _ block.PutOpts) error { if err := verifyObjectPointer(obj); err != nil { return err } @@ -90,7 +90,6 @@ func (a *Adapter) Put(_ context.Context, obj block.ObjectPointer, _ int64, reade } key := getKey(obj) a.data[key] = data - a.properties[key] = block.Properties(opts) return nil } diff --git a/block/s3/adapter.go b/block/s3/adapter.go index 9df24ded..b412d2af 100644 --- a/block/s3/adapter.go +++ b/block/s3/adapter.go @@ -200,9 +200,7 @@ func (a *Adapter) Put(ctx context.Context, obj block.ObjectPointer, sizeBytes in if sizeBytes == 0 { putObject.Body = http.NoBody } - if opts.StorageClass != nil { - putObject.StorageClass = types.StorageClass(*opts.StorageClass) - } + if a.ServerSideEncryption != "" { putObject.ServerSideEncryption = types.ServerSideEncryption(a.ServerSideEncryption) } @@ -755,7 +753,7 @@ func (a *Adapter) RuntimeStats() map[string]string { } } -func (a *Adapter) managerUpload(ctx context.Context, obj block.ObjectPointer, reader io.Reader, opts block.PutOpts) error { +func (a *Adapter) managerUpload(ctx context.Context, obj block.ObjectPointer, reader io.Reader, _ block.PutOpts) error { bucket, key, _, err := a.extractParamsFromObj(obj) if err != nil { return err @@ -768,9 +766,7 @@ func (a *Adapter) managerUpload(ctx context.Context, obj block.ObjectPointer, re Key: aws.String(key), Body: reader, } - if opts.StorageClass != nil { - input.StorageClass = types.StorageClass(*opts.StorageClass) - } + if a.ServerSideEncryption != "" { input.ServerSideEncryption = types.ServerSideEncryption(a.ServerSideEncryption) } diff --git a/controller/coremgr.go b/controller/coremgr.go deleted file mode 100644 index fd7ff231..00000000 --- a/controller/coremgr.go +++ /dev/null @@ -1,155 +0,0 @@ -package controller - -import ( - "context" - "fmt" - "io" - "os" - "strings" - - "github.com/jiaozifs/jiaozifs/utils/pathutil" - - hash2 "github.com/jiaozifs/jiaozifs/utils/hash" - - "github.com/jiaozifs/jiaozifs/block" - - "github.com/jiaozifs/jiaozifs/models/filemode" - - "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/models" -) - -var ( - ErrPathNotFound = fmt.Errorf("path not found") -) - -type CoreMgr struct { - UserRepo models.IUserRepo - Repository models.RepositoryRepo - Object models.ObjectRepo - Ref models.RefRepo -} - -func (core *CoreMgr) GetBlobByPath(ctx context.Context, treeId uuid.UUID, path string) (*models.Blob, *models.TreeEntry, error) { - rootNode, err := core.Object.TreeNode(ctx, treeId) - if err != nil { - return nil, nil, err - } - - fileSegs := strings.Split(path, "/") - pathLen := len(fileSegs) - 1 - for index, seg := range fileSegs { - for _, node := range rootNode.SubObject { - if node.Name == seg { - if index == pathLen { - //last one - if node.Mode == filemode.Regular || node.Mode == filemode.Executable { - blob, err := core.Object.Blob(ctx, node.ID) - if err != nil { - return nil, nil, err - } - return blob, &node, nil - } - return nil, nil, ErrPathNotFound - } - if node.Mode != filemode.Dir { - return nil, nil, ErrPathNotFound - } - rootNode, err = core.Object.TreeNode(ctx, treeId) - if err != nil { - return nil, nil, err - } - break - } - continue - } - return nil, nil, ErrPathNotFound - } - return nil, nil, ErrPathNotFound -} - -func (core *CoreMgr) WriteBlob(ctx context.Context, adapter block.Adapter, bucketName string, body io.Reader, contentLength int64, opts block.PutOpts) (*models.Blob, error) { - // handle the upload itself - hashReader := hash2.NewHashingReader(body, hash2.HashFunctionMD5) - hash := hash2.Hash(hashReader.Md5.Sum(nil)) - tempf, err := os.CreateTemp("", "*") - if err != nil { - return nil, err - } - _, err = io.Copy(tempf, body) - if err != nil { - return nil, err - } - - _, err = tempf.Seek(io.SeekStart, 0) - if err != nil { - return nil, err - } - - defer func() { - name := tempf.Name() - _ = tempf.Close() - _ = os.RemoveAll(name) - }() - - address := pathutil.PathOfHash(hash) - err = adapter.Put(ctx, block.ObjectPointer{ - StorageNamespace: bucketName, - IdentifierType: block.IdentifierTypeRelative, - Identifier: address, - }, contentLength, tempf, opts) - if err != nil { - return nil, err - } - - return &models.Blob{ - PhysicalAddress: address, - RelativePath: true, - Hash: hash, - Size: hashReader.CopiedSize, - }, nil -} - -func (core *CoreMgr) ApplyChange(ctx context.Context, treeID uuid.UUID, changeMode models.Action, path string) (uuid.UUID, error) { - treeNode, err := core.Object.TreeNode(ctx, treeID) - if err != nil { - return uuid.Nil, err - } - - subDir := func(ctx context.Context, tn *models.TreeNode, name string) (*models.TreeNode, error) { - for _, node := range tn.SubObject { - if node.Name == name && node.Mode == filemode.Dir { - return core.Object.TreeNode(ctx, node.ID) - } - } - return nil, ErrPathNotFound - } - subFile := func(ctx context.Context, tn *models.TreeNode, name string) (*models.Blob, error) { - for _, node := range tn.SubObject { - if node.Name == name && (node.Mode == filemode.Regular || node.Mode == filemode.Executable) { - return core.Object.Blob(ctx, node.ID) - } - } - return nil, ErrPathNotFound - } - - fileSegs := strings.Split(path, "/") - pathLen := len(fileSegs) - 1 - - for index, seg := range fileSegs { - if index == pathLen { - blob, err := subFile(ctx, treeNode, seg) - if err != nil { - return uuid.Nil, err - } - - } else { - treeNode, err = subDir(ctx, treeNode, seg) - if err != nil { - return uuid.Nil, err - } - } - return uuid.Nil, ErrPathNotFound - } - return uuid.Nil, ErrPathNotFound -} diff --git a/controller/object_ctl.go b/controller/object_ctl.go index 06cc78ca..9d6f2ddd 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -9,6 +9,10 @@ import ( "net/http" "time" + "github.com/jiaozifs/jiaozifs/config" + + "github.com/jiaozifs/jiaozifs/versionmgr" + "github.com/jiaozifs/jiaozifs/models/filemode" "github.com/go-openapi/swag" @@ -29,12 +33,13 @@ type ObjectController struct { BlockAdapter block.Adapter - Core *CoreMgr StashRepo models.StashRepo UserRepo models.IUserRepo Repository models.RepositoryRepo Object models.ObjectRepo Ref models.RefRepo + + Cfg config.BlockStoreConfig } func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, user string, repository string, params api.DeleteObjectParams) { //nolint @@ -48,7 +53,7 @@ func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsRespon } func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, userName string, repository string, params api.HeadObjectParams) { //nolint - user, err := oct.UserRepo.GetByName(ctx, userName) + user, err := oct.UserRepo.Get(ctx, &models.GetUserParam{Name: utils.String(userName)}) if err != nil { w.Error(err) return @@ -78,19 +83,40 @@ func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsRespo return } - blob, treeEntry, err := oct.Core.GetBlobByPath(ctx, commit.ID, params.Path) + treeOp := versionmgr.NewTreeOp(oct.Object) + + treeNode, err := oct.Object.TreeNode(ctx, commit.TreeHash) + if err != nil { + w.Error(err) + return + } + + existNodes, missingPath, err := treeOp.MatchPath(ctx, treeNode, params.Path) + if err != nil { + w.Error(err) + return + } + + if len(missingPath) == 0 { + w.Error(versionmgr.ErrPathNotFound) + return + } + + blobWithName := existNodes[len(existNodes)-1] + + blob, err := oct.Object.Blob(ctx, blobWithName.Node.Hash) if err != nil { w.Error(err) return } //lookup files - etag := httputil.ETag(blob.Hash.Hex()) + etag := httputil.ETag(blobWithName.Node.Hash.Hex()) w.Header().Set("ETag", etag) - lastModified := httputil.HeaderTimestamp(blob.CreatedAt) + lastModified := httputil.HeaderTimestamp(blobWithName.Node.CreatedAt) w.Header().Set("Last-Modified", lastModified) w.Header().Set("Accept-Ranges", "bytes") - w.Header().Set("Content-Type", httputil.ExtensionsByType(treeEntry.Name)) + w.Header().Set("Content-Type", httputil.ExtensionsByType(blobWithName.Name)) // for security, make sure the browser and any proxies en route don't cache the response w.Header().Set("Cache-Control", "no-store, must-revalidate") w.Header().Set("Expires", "0") @@ -111,21 +137,6 @@ func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsRespo } func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, userName string, repository string, params api.UploadObjectParams) { //nolint - user, err := oct.UserRepo.GetByName(ctx, userName) - if err != nil { - w.Error(err) - return - } - - repo, err := oct.Repository.Get(ctx, &models.GetRepoParams{ - CreateID: user.ID, - Name: utils.String(repository), - }) - if err != nil { - w.Error(err) - return - } - // read request body parse multipart for "content" and upload the data contentType := r.Header.Get("Content-Type") mediaType, p, err := mime.ParseMediaType(contentType) @@ -133,11 +144,11 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes w.Error(err) return } - + treeOp := versionmgr.TreeOp{Object: oct.Object} var blob *models.Blob if mediaType != "multipart/form-data" { // handle non-multipart, direct content upload - blob, err = oct.Core.WriteBlob(ctx, oct.BlockAdapter, repo.StorageNamespace, r.Body, r.ContentLength, block.PutOpts{}) + blob, err = treeOp.WriteBlob(ctx, oct.BlockAdapter, r.Body, r.ContentLength, block.PutOpts{}) if err != nil { w.Error(err) return @@ -165,7 +176,7 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes partName := part.FormName() if partName == "content" { // upload the first "content" and exit the loop - blob, err = oct.Core.WriteBlob(ctx, oct.BlockAdapter, repo.StorageNamespace, part, -1, block.PutOpts{}) + blob, err = treeOp.WriteBlob(ctx, oct.BlockAdapter, part, -1, block.PutOpts{}) if err != nil { _ = part.Close() w.Error(err) @@ -176,12 +187,26 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes _ = part.Close() } if !contentUploaded { - err := fmt.Errorf("multipart upload missing key 'content': %w", http.ErrMissingFile) - w.Error(err) + w.Error(fmt.Errorf("multipart upload missing key 'content': %w", http.ErrMissingFile)) return } } + user, err := oct.UserRepo.Get(ctx, &models.GetUserParam{Name: utils.String(userName)}) + if err != nil { + w.Error(err) + return + } + + repo, err := oct.Repository.Get(ctx, &models.GetRepoParams{ + CreateID: user.ID, + Name: utils.String(repository), + }) + if err != nil { + w.Error(err) + return + } + stash, err := oct.StashRepo.Get(ctx, &models.GetStashParam{ RepositoryID: repo.ID, CreateID: user.ID, @@ -191,28 +216,32 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes return } - //apply change to stash - //todo write block to stash - identifierType := block.IdentifierTypeFull - if blob.RelativePath { - identifierType = block.IdentifierTypeRelative + workingTreeID, err := oct.Object.TreeNode(ctx, stash.CurrentTree) + if err != nil { + w.Error(err) + return + } + + newRoot, err := treeOp.AddLeaf(ctx, workingTreeID, params.Path, blob) + if err != nil { + w.Error(err) + return } - qk, err := oct.BlockAdapter.ResolveNamespace(repo.StorageNamespace, blob.PhysicalAddress, identifierType) + err = oct.StashRepo.UpdateCurrentHash(ctx, stash.ID, newRoot.Hash) if err != nil { w.Error(err) return } response := api.ObjectStats{ - Checksum: blob.Hash.Hex(), - Mtime: time.Now().Unix(), - Path: params.Path, - PathMode: utils.Uint32(uint32(filemode.Regular)), - PhysicalAddress: qk.Format(), - SizeBytes: swag.Int64(blob.Size), - ContentType: &contentType, - Metadata: &api.ObjectUserMetadata{}, + Checksum: blob.Hash.Hex(), + Mtime: time.Now().Unix(), + Path: params.Path, + PathMode: utils.Uint32(uint32(filemode.Regular)), + SizeBytes: swag.Int64(blob.Size), + ContentType: &contentType, + Metadata: &api.ObjectUserMetadata{}, } w.JSON(response) } diff --git a/controller/user_ctl.go b/controller/user_ctl.go index 221af5e6..9b0e7f7b 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -1,6 +1,7 @@ package controller import ( + "context" "encoding/json" "net/http" @@ -12,6 +13,10 @@ import ( "go.uber.org/fx" ) +const ( + AuthHeader = "Authorization" +) + type UserController struct { fx.In @@ -19,57 +24,52 @@ type UserController struct { Config *config.Config } -func (A UserController) Login(w *api.JiaozifsResponse, r *http.Request) { - ctx := r.Context() +func (A UserController) Login(ctx context.Context, w *api.JiaozifsResponse, r *http.Request) { // Decode requestBody var login auth.Login decoder := json.NewDecoder(r.Body) if err := decoder.Decode(&login); err != nil { - w.RespError(err) + w.Error(err) return } - // Perform login - resp, err := login.Login(ctx, A.Repo, A.Config) + // perform login + authToken, err := login.Login(ctx, A.Repo, A.Config) if err != nil { - w.RespError(err) + w.Error(err) return } - - // resp - w.RespJSON(resp) + w.JSON(authToken) } -func (A UserController) Register(w *api.JiaozifsResponse, r *http.Request) { - ctx := r.Context() +func (A UserController) Register(ctx context.Context, w *api.JiaozifsResponse, r *http.Request) { // Decode requestBody var register auth.Register decoder := json.NewDecoder(r.Body) if err := decoder.Decode(®ister); err != nil { - w.RespError(err) + w.Error(err) + return } - // Perform register + // perform register err := register.Register(ctx, A.Repo) if err != nil { - w.RespError(err) + w.Error(err) return } - // resp - w.RespJSON("registration success") + w.OK() } -func (A UserController) GetUserInfo(w *api.JiaozifsResponse, r *http.Request) { - ctx := r.Context() +func (A UserController) GetUserInfo(ctx context.Context, w *api.JiaozifsResponse, r *http.Request) { // Get token from Header - tokenString := r.Header.Get("Authorization") + tokenString := r.Header.Get(AuthHeader) userInfo := &auth.UserInfo{Token: tokenString} - // Perform GetUserInfo - info, err := userInfo.UserProfile(ctx, A.Repo, A.Config) + // perform GetUserInfo + usrInfo, err := userInfo.UserProfile(ctx, A.Repo, A.Config) if err != nil { - w.RespError(err) + w.Error(err) return } - // resp - w.RespJSON(info) + + w.JSON(usrInfo) } diff --git a/go.mod b/go.mod index 295405ca..c2177f4c 100644 --- a/go.mod +++ b/go.mod @@ -90,6 +90,7 @@ require ( github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/emirpasic/gods v1.18.1 // indirect github.com/fatih/color v1.15.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect diff --git a/go.sum b/go.sum index 5897a877..644003fe 100644 --- a/go.sum +++ b/go.sum @@ -120,18 +120,23 @@ github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -149,6 +154,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -171,6 +178,7 @@ github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vz github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= @@ -183,6 +191,7 @@ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9 github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= @@ -292,11 +301,13 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= @@ -344,6 +355,7 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -353,6 +365,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oapi-codegen/nethttp-middleware v1.0.1 h1:ZWvwfnMU0eloHX1VEJmQscQm3741t0vCm0eSIie1NIo= github.com/oapi-codegen/nethttp-middleware v1.0.1/go.mod h1:P7xtAvpoqNB+5obR9qRCeefH7YlXWSK3KgPs/9WB8tE= github.com/oapi-codegen/runtime v1.1.0 h1:rJpoNUawn5XTvekgfkvSZr0RqEnoYpFkyvrzfWeFKWM= @@ -363,6 +377,8 @@ github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrB github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v1.1.7 h1:y2EZDS8sNng4Ksf0GUYNhKbTShZJPJg1FiXJNH/uoCk= github.com/opencontainers/runc v1.1.7/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= @@ -405,6 +421,8 @@ github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9c github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= @@ -435,6 +453,7 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/thanhpk/randstr v1.0.6 h1:psAOktJFD4vV9NEVb3qkhRSMvYh4ORRaj1+w/hn4B+o= github.com/thanhpk/randstr v1.0.6/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= @@ -451,6 +470,9 @@ github.com/uptrace/bun/driver/pgdriver v1.1.16 h1:b/NiSXk6Ldw7KLfMLbOqIkm4odHd7Q github.com/uptrace/bun/driver/pgdriver v1.1.16/go.mod h1:Rmfbc+7lx1z/umjMyAxkOHK81LgnGj71XC5YpA6k1vU= github.com/uptrace/bun/extra/bundebug v1.1.16 h1:SgicRQGtnjhrIhlYOxdkOm1Em4s6HykmT3JblHnoTBM= github.com/uptrace/bun/extra/bundebug v1.1.16/go.mod h1:SkiOkfUirBiO1Htc4s5bQKEq+JSeU1TkBVpMsPz2ePM= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= @@ -527,6 +549,7 @@ golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPI golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= diff --git a/models/object.go b/models/object.go index 12b18232..fbaef3c6 100644 --- a/models/object.go +++ b/models/object.go @@ -4,7 +4,6 @@ import ( "context" "time" - "github.com/google/uuid" "github.com/jiaozifs/jiaozifs/models/filemode" "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/uptrace/bun" @@ -36,38 +35,91 @@ type Signature struct { type TreeEntry struct { Name string `bun:"name"` Mode filemode.FileMode `bun:"mode"` - ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` Hash hash.Hash `bun:"hash"` } type Blob struct { - bun.BaseModel `bun:"table:object"` - ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` - Hash hash.Hash `bun:"hash,type:bytea"` - Type ObjectType `bun:"type"` - PhysicalAddress string `bun:"physical_address"` - RelativePath bool `bun:"relative_path"` - Size int64 `bun:"size"` + bun.BaseModel `bun:"table:object"` + Hash hash.Hash `bun:"hash,pk,type:bytea"` + Type ObjectType `bun:"type"` + Size int64 `bun:"size"` CreatedAt time.Time `bun:"created_at"` UpdatedAt time.Time `bun:"updated_at"` } +func (blob *Blob) Object() *Object { + return &Object{ + Hash: blob.Hash, + Type: blob.Type, + Size: blob.Size, + CreatedAt: blob.CreatedAt, + UpdatedAt: blob.UpdatedAt, + } +} + type TreeNode struct { bun.BaseModel `bun:"table:object"` - ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` - Hash hash.Hash `bun:"hash,type:bytea"` + Hash hash.Hash `bun:"hash,pk,type:bytea"` Type ObjectType `bun:"type"` - SubObject []TreeEntry `bun:"subObj,type:jsonb"` + SubObjects []TreeEntry `bun:"subObjs,type:jsonb"` CreatedAt time.Time `bun:"created_at"` UpdatedAt time.Time `bun:"updated_at"` } +func NewTreeNode(subObjects ...TreeEntry) (*TreeNode, error) { + newTree := &TreeNode{ + Type: TreeObject, + SubObjects: subObjects, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + hash, err := newTree.GetHash() + if err != nil { + return nil, err + } + newTree.Hash = hash + return newTree, nil +} + +func (tn *TreeNode) Object() *Object { + return &Object{ + Hash: tn.Hash, + Type: tn.Type, + SubObjects: tn.SubObjects, + CreatedAt: tn.CreatedAt, + UpdatedAt: tn.UpdatedAt, + } +} + +func (tn *TreeNode) GetHash() (hash.Hash, error) { + hasher := hash.NewHasher(hash.Md5) + err := hasher.WriteInt8(int8(tn.Type)) + if err != nil { + return nil, err + } + for _, obj := range tn.SubObjects { + _, err = hasher.Write(obj.Hash) + if err != nil { + return nil, err + } + err = hasher.WriteString(obj.Name) + if err != nil { + return nil, err + } + err = hasher.WriteUint32(uint32(obj.Mode)) + if err != nil { + return nil, err + } + } + + return hash.Hash(hasher.Md5.Sum(nil)), nil +} + type Commit struct { bun.BaseModel `bun:"table:object"` - ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` - Hash hash.Hash `bun:"hash,type:bytea"` + Hash hash.Hash `bun:"hash,pk,type:bytea"` Type ObjectType `bun:"type"` //////********commit********//////// // Author is the original author of the commit. @@ -81,7 +133,7 @@ type Commit struct { // Message is the commit/tag message, contains arbitrary text. Message string `bun:"message"` // TreeHash is the hash of the root tree of the commit. - TreeId uuid.UUID `bun:"tree_id,type:uuid,notnull"` + TreeHash hash.Hash `bun:"tree+hash,type:bytea,notnull"` // ParentHashes are the hashes of the parent commits of the commit. ParentHashes []hash.Hash `bun:"parent_hashes,type:bytea[]"` @@ -89,10 +141,24 @@ type Commit struct { UpdatedAt time.Time `bun:"updated_at"` } +func (commit *Commit) Object() *Object { + return &Object{ + Hash: commit.Hash, + Type: commit.Type, + Author: commit.Author, + Committer: commit.Committer, + MergeTag: commit.MergeTag, + Message: commit.Message, + TreeHash: commit.TreeHash, + ParentHashes: commit.ParentHashes, + CreatedAt: commit.CreatedAt, + UpdatedAt: commit.UpdatedAt, + } +} + type Tag struct { bun.BaseModel `bun:"table:object"` - ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` - Hash hash.Hash `bun:"hash,type:bytea"` + Hash hash.Hash `bun:"hash,pk,type:bytea"` Type ObjectType `bun:"type"` //////********commit********//////// // Name of the tag. @@ -110,14 +176,27 @@ type Tag struct { UpdatedAt time.Time `bun:"updated_at"` } +func (tag *Tag) Object() *Object { + return &Object{ + Hash: tag.Hash, + Type: tag.Type, + Name: tag.Name, + Tagger: tag.Tagger, + TargetType: tag.TargetType, + Target: tag.Target, + Message: tag.Message, + CreatedAt: tag.CreatedAt, + UpdatedAt: tag.UpdatedAt, + } +} + type Object struct { bun.BaseModel `bun:"table:object"` - ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` - Hash hash.Hash `bun:"hash,type:bytea"` + Hash hash.Hash `bun:"hash,pk,type:bytea"` Type ObjectType `bun:"type"` Size int64 `bun:"size"` //tree - SubObject []TreeEntry `bun:"subObj,type:jsonb"` + SubObjects []TreeEntry `bun:"subObjs,type:jsonb"` //////********commit********//////// // Author is the original author of the commit. @@ -149,8 +228,57 @@ type Object struct { UpdatedAt time.Time `bun:"updated_at"` } +func (obj *Object) Blob() *Blob { + return &Blob{ + Hash: obj.Hash, + Type: obj.Type, + Size: obj.Size, + CreatedAt: obj.CreatedAt, + UpdatedAt: obj.UpdatedAt, + } +} + +func (obj *Object) TreeNode() *TreeNode { + return &TreeNode{ + Hash: obj.Hash, + Type: obj.Type, + SubObjects: obj.SubObjects, + CreatedAt: obj.CreatedAt, + UpdatedAt: obj.UpdatedAt, + } +} + +func (obj *Object) Commit() *Commit { + return &Commit{ + Hash: obj.Hash, + Type: obj.Type, + Author: obj.Author, + Committer: obj.Committer, + MergeTag: obj.MergeTag, + Message: obj.Message, + TreeHash: obj.TreeHash, + ParentHashes: obj.ParentHashes, + CreatedAt: obj.CreatedAt, + UpdatedAt: obj.UpdatedAt, + } +} + +func (obj *Object) Tag() *Tag { + return &Tag{ + Hash: obj.Hash, + Type: obj.Type, + Name: obj.Name, + Tagger: obj.Tagger, + TargetType: obj.TargetType, + Target: obj.Target, + Message: obj.Message, + CreatedAt: obj.CreatedAt, + UpdatedAt: obj.UpdatedAt, + } +} + type GetObjParams struct { - ID uuid.UUID + Hash hash.Hash } type IObjectRepo interface { @@ -158,10 +286,10 @@ type IObjectRepo interface { Get(ctx context.Context, params *GetObjParams) (*Object, error) Count(ctx context.Context) (int, error) List(ctx context.Context) ([]Object, error) - Blob(ctx context.Context, id uuid.UUID) (*Blob, error) - TreeNode(ctx context.Context, id uuid.UUID) (*TreeNode, error) - Commit(ctx context.Context, id uuid.UUID) (*Commit, error) - Tag(ctx context.Context, id uuid.UUID) (*Tag, error) + Blob(ctx context.Context, hash hash.Hash) (*Blob, error) + TreeNode(ctx context.Context, hash hash.Hash) (*TreeNode, error) + Commit(ctx context.Context, hash hash.Hash) (*Commit, error) + Tag(ctx context.Context, hash hash.Hash) (*Tag, error) } var _ IObjectRepo = (*ObjectRepo)(nil) @@ -175,7 +303,7 @@ func NewObjectRepo(db *bun.DB) IObjectRepo { } func (o ObjectRepo) Insert(ctx context.Context, obj *Object) (*Object, error) { - _, err := o.db.NewInsert().Model(obj).Exec(ctx) + _, err := o.db.NewInsert().Model(obj).Ignore().Exec(ctx) if err != nil { return nil, err } @@ -186,31 +314,31 @@ func (o ObjectRepo) Get(ctx context.Context, params *GetObjParams) (*Object, err repo := &Object{} query := o.db.NewSelect().Model(repo) - if uuid.Nil != params.ID { - query = query.Where("id = ?", params.ID) + if params.Hash != nil { + query = query.Where("hash = ?", params.Hash) } - return repo, query.Scan(ctx, repo) + return repo, query.Limit(1).Scan(ctx, repo) } -func (o ObjectRepo) Blob(ctx context.Context, id uuid.UUID) (*Blob, error) { +func (o ObjectRepo) Blob(ctx context.Context, hash hash.Hash) (*Blob, error) { blob := &Blob{} - return blob, o.db.NewSelect().Model(blob).Where("id = ?", id).Scan(ctx) + return blob, o.db.NewSelect().Model(blob).Limit(1).Where("hash = ?", hash).Scan(ctx) } -func (o ObjectRepo) TreeNode(ctx context.Context, id uuid.UUID) (*TreeNode, error) { +func (o ObjectRepo) TreeNode(ctx context.Context, hash hash.Hash) (*TreeNode, error) { tree := &TreeNode{} - return tree, o.db.NewSelect().Model(tree).Where("id = ?", id).Scan(ctx) + return tree, o.db.NewSelect().Model(tree).Limit(1).Where("hash = ?", hash).Scan(ctx) } -func (o ObjectRepo) Commit(ctx context.Context, id uuid.UUID) (*Commit, error) { +func (o ObjectRepo) Commit(ctx context.Context, hash hash.Hash) (*Commit, error) { commit := &Commit{} - return commit, o.db.NewSelect().Model(commit).Where("id = ?", id).Scan(ctx) + return commit, o.db.NewSelect().Model(commit).Limit(1).Where("hash = ?", hash).Scan(ctx) } -func (o ObjectRepo) Tag(ctx context.Context, id uuid.UUID) (*Tag, error) { +func (o ObjectRepo) Tag(ctx context.Context, hash hash.Hash) (*Tag, error) { tag := &Tag{} - return tag, o.db.NewSelect().Model(tag).Where("id = ?", id).Scan(ctx) + return tag, o.db.NewSelect().Model(tag).Limit(1).Where("hash = ?", hash).Scan(ctx) } func (o ObjectRepo) Count(ctx context.Context) (int, error) { @@ -218,6 +346,6 @@ func (o ObjectRepo) Count(ctx context.Context) (int, error) { } func (o ObjectRepo) List(ctx context.Context) ([]Object, error) { - obj := []Object{} + var obj []Object return obj, o.db.NewSelect().Model(&obj).Scan(ctx) } diff --git a/models/object_test.go b/models/object_test.go index a7923575..b7582834 100644 --- a/models/object_test.go +++ b/models/object_test.go @@ -6,14 +6,14 @@ import ( "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" - "github.com/google/uuid" "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/testhelper" "github.com/stretchr/testify/require" ) func TestObjectRepo_Insert(t *testing.T) { ctx := context.Background() - postgres, db := setup(ctx, t) + postgres, _, db := testhelper.SetupDatabase(ctx, t) defer postgres.Stop() //nolint repo := models.NewObjectRepo(db) @@ -22,7 +22,7 @@ func TestObjectRepo_Insert(t *testing.T) { require.NoError(t, gofakeit.Struct(objModel)) newObj, err := repo.Insert(ctx, objModel) require.NoError(t, err) - require.NotEqual(t, uuid.Nil, newObj.ID) + require.NotEqual(t, nil, newObj.Hash) count, err := repo.Count(ctx) require.NoError(t, err) @@ -32,7 +32,9 @@ func TestObjectRepo_Insert(t *testing.T) { require.NoError(t, err) require.Equal(t, 1, len(list)) - ref, err := repo.Get(ctx, newObj.ID) + ref, err := repo.Get(ctx, &models.GetObjParams{ + Hash: newObj.Hash, + }) require.NoError(t, err) require.True(t, cmp.Equal(newObj, ref, dbTimeCmpOpt)) diff --git a/models/ref.go b/models/ref.go index f675f3a8..f7caffb2 100644 --- a/models/ref.go +++ b/models/ref.go @@ -4,6 +4,8 @@ import ( "context" "time" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/google/uuid" "github.com/uptrace/bun" ) @@ -13,7 +15,7 @@ type Ref struct { ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` // RepositoryId which repository this branch belong RepositoryID uuid.UUID `bun:"repository_id,type:uuid,notnull"` - CommitHash uuid.UUID `bun:"commit_hash,type:uuid,notnull"` + CommitHash hash.Hash `bun:"commit_hash,type:bytea,notnull"` // Path name/path of branch Name string `bun:"name,notnull"` // Description diff --git a/models/ref_test.go b/models/ref_test.go index 8e78a565..10a2c306 100644 --- a/models/ref_test.go +++ b/models/ref_test.go @@ -8,12 +8,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/uuid" "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/testhelper" "github.com/stretchr/testify/require" ) -func TestRefRepo_Insert(t *testing.T) { +func TestRefRepoInsert(t *testing.T) { ctx := context.Background() - postgres, db := setup(ctx, t) + postgres, _, db := testhelper.SetupDatabase(ctx, t) defer postgres.Stop() //nolint repo := models.NewRefRepo(db) @@ -24,7 +25,9 @@ func TestRefRepo_Insert(t *testing.T) { require.NoError(t, err) require.NotEqual(t, uuid.Nil, newRef.ID) - ref, err := repo.Get(ctx, newRef.ID) + ref, err := repo.Get(ctx, &models.GetRefParams{ + ID: newRef.ID, + }) require.NoError(t, err) require.True(t, cmp.Equal(refModel, ref, dbTimeCmpOpt)) diff --git a/models/repository.go b/models/repository.go index fe56b4fc..d781d814 100644 --- a/models/repository.go +++ b/models/repository.go @@ -9,20 +9,19 @@ import ( ) type Repository struct { - bun.BaseModel `bun:"table:repository"` - ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` - Name string `bun:"name,notnull"` - StorageNamespace string `bun:"storage_namespace,notnull"` - Description string `bun:"description"` - HEAD uuid.UUID `bun:"head,type:uuid,notnull"` - CreateID uuid.UUID `bun:"create_id,type:uuid,notnull"` + bun.BaseModel `bun:"table:repository"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` + Name string `bun:"name,notnull"` + Description string `bun:"description"` + HEAD string `bun:"head,notnull"` + CreateID uuid.UUID `bun:"create_id,type:uuid,notnull"` CreatedAt time.Time `bun:"created_at"` UpdatedAt time.Time `bun:"updated_at"` } type GetRepoParams struct { - Id uuid.UUID + ID uuid.UUID CreateID uuid.UUID Name *string } @@ -54,8 +53,8 @@ func (r *RepositoryRepo) Get(ctx context.Context, params *GetRepoParams) (*Repos repo := &Repository{} query := r.db.NewSelect().Model(repo) - if uuid.Nil != params.Id { - query = query.Where("id = ?", params.Id) + if uuid.Nil != params.ID { + query = query.Where("id = ?", params.ID) } if uuid.Nil != params.CreateID { diff --git a/models/repository_test.go b/models/repository_test.go index 3b2e3e72..6319a541 100644 --- a/models/repository_test.go +++ b/models/repository_test.go @@ -8,12 +8,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/uuid" "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/testhelper" "github.com/stretchr/testify/require" ) func TestRepositoryRepo_Insert(t *testing.T) { ctx := context.Background() - postgres, db := setup(ctx, t) + postgres, _, db := testhelper.SetupDatabase(ctx, t) defer postgres.Stop() //nolint repo := models.NewRepositoryRepo(db) @@ -24,7 +25,9 @@ func TestRepositoryRepo_Insert(t *testing.T) { require.NoError(t, err) require.NotEqual(t, uuid.Nil, newUser.ID) - user, err := repo.Get(ctx, newUser.ID) + user, err := repo.Get(ctx, &models.GetRepoParams{ + ID: newUser.ID, + }) require.NoError(t, err) require.True(t, cmp.Equal(repoModel, user, dbTimeCmpOpt)) diff --git a/models/user.go b/models/user.go index 75672186..7b6ba929 100644 --- a/models/user.go +++ b/models/user.go @@ -23,14 +23,19 @@ type User struct { UpdatedAt time.Time `bun:"updated_at"` } +type GetUserParam struct { + ID uuid.UUID + Name *string + Email *string +} + +type CountUserParams = GetUserParam + type IUserRepo interface { - Get(ctx context.Context, id uuid.UUID) (*User, error) - GetByName(ctx context.Context, name string) (*User, error) + Get(ctx context.Context, params *GetUserParam) (*User, error) + Count(ctx context.Context, params *CountUserParams) (int, error) Insert(ctx context.Context, user *User) (*User, error) - GetEPByName(ctx context.Context, name string) (string, error) - GetUserByName(ctx context.Context, name string) (*User, error) - GetUserByEmail(ctx context.Context, email string) (*User, error) } var _ IUserRepo = (*UserRepo)(nil) @@ -43,14 +48,41 @@ func NewUserRepo(db *bun.DB) IUserRepo { return &UserRepo{db} } -func (userRepo *UserRepo) GetByName(ctx context.Context, name string) (*User, error) { +func (userRepo *UserRepo) Get(ctx context.Context, params *GetUserParam) (*User, error) { user := &User{} - return user, userRepo.db.NewSelect().Model(user).Where("name = ?", name).Scan(ctx) + query := userRepo.db.NewSelect().Model(user) + + if uuid.Nil != params.ID { + query = query.Where("id = ?", params.ID) + } + + if params.Name != nil { + query = query.Where("name = ?", params.Name) + } + + if params.Email != nil { + query = query.Where("email = ?", *params.Email) + } + + return user, query.Limit(1).Scan(ctx) } -func (userRepo *UserRepo) Get(ctx context.Context, id uuid.UUID) (*User, error) { - user := &User{} - return user, userRepo.db.NewSelect().Model(user).Where("id = ?", id).Scan(ctx) +func (userRepo *UserRepo) Count(ctx context.Context, params *GetUserParam) (int, error) { + query := userRepo.db.NewSelect().Model((*User)(nil)) + + if uuid.Nil != params.ID { + query = query.Where("id = ?", params.ID) + } + + if params.Name != nil { + query = query.Where("name = ?", params.Name) + } + + if params.Email != nil { + query = query.Where("email = ?", *params.Email) + } + + return query.Count(ctx) } func (userRepo *UserRepo) Insert(ctx context.Context, user *User) (*User, error) { @@ -63,19 +95,8 @@ func (userRepo *UserRepo) Insert(ctx context.Context, user *User) (*User, error) func (userRepo *UserRepo) GetEPByName(ctx context.Context, name string) (string, error) { var ep string - return ep, userRepo.DB.NewSelect(). + return ep, userRepo.db.NewSelect(). Model((*User)(nil)).Column("encrypted_password"). Where("name = ?", name). Scan(ctx, &ep) } - -func (userRepo *UserRepo) GetUserByName(ctx context.Context, name string) (*User, error) { - user := &User{} - return user, userRepo.DB.NewSelect().Model(user).Where("name = ?", name).Scan(ctx) -} - -func (userRepo *UserRepo) GetUserByEmail(ctx context.Context, email string) (*User, error) { - user := &User{} - return user, userRepo.DB.NewSelect(). - Model(user).Where("email = ?", email).Scan(ctx) -} diff --git a/models/user_test.go b/models/user_test.go index d0ffc743..9778ccfc 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -2,53 +2,31 @@ package models_test import ( "context" - "fmt" "testing" "time" + "github.com/jiaozifs/jiaozifs/utils" + + "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" "github.com/google/uuid" - "github.com/uptrace/bun" - - "github.com/jiaozifs/jiaozifs/models/migrations" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/config" - "github.com/phayes/freeport" - "go.uber.org/fx/fxtest" - "github.com/stretchr/testify/require" - - embeddedpostgres "github.com/fergusstrange/embedded-postgres" ) var dbTimeCmpOpt = cmp.Comparer(func(x, y time.Time) bool { return x.Unix() == y.Unix() }) -func setup(ctx context.Context, t *testing.T) (*embeddedpostgres.EmbeddedPostgres, *bun.DB) { - port, err := freeport.GetFreePort() - require.NoError(t, err) - postgres := embeddedpostgres.NewDatabase(embeddedpostgres.DefaultConfig().Port(uint32(port)).Database("jiaozifs")) - err = postgres.Start() - require.NoError(t, err) - - db, err := models.SetupDatabase(ctx, fxtest.NewLifecycle(t), &config.DatabaseConfig{Debug: true, Connection: fmt.Sprintf(testConnTmpl, port)}) - require.NoError(t, err) - - err = migrations.MigrateDatabase(ctx, db) - require.NoError(t, err) - return postgres, db -} - func TestNewUserRepo(t *testing.T) { ctx := context.Background() - postgres, db := setup(ctx, t) + postgres, _, db := testhelper.SetupDatabase(ctx, t) defer postgres.Stop() //nolint repo := models.NewUserRepo(db) @@ -59,7 +37,7 @@ func TestNewUserRepo(t *testing.T) { require.NoError(t, err) require.NotEqual(t, uuid.Nil, newUser.ID) - user, err := repo.Get(ctx, newUser.ID) + user, err := repo.Get(ctx, &models.GetUserParam{ID: newUser.ID}) require.NoError(t, err) require.True(t, cmp.Equal(userModel.UpdatedAt, user.UpdatedAt, dbTimeCmpOpt)) @@ -68,11 +46,11 @@ func TestNewUserRepo(t *testing.T) { require.NoError(t, err) require.True(t, cmp.Equal(userModel.EncryptedPassword, ep)) - userByEmail, err := repo.GetUserByEmail(ctx, newUser.Email) + userByEmail, err := repo.Get(ctx, &models.GetUserParam{Email: utils.String(newUser.Email)}) require.NoError(t, err) require.True(t, cmp.Equal(userModel.UpdatedAt, userByEmail.UpdatedAt, dbTimeCmpOpt)) - userByName, err := repo.GetUserByName(ctx, newUser.Name) + userByName, err := repo.Get(ctx, &models.GetUserParam{Name: utils.String(newUser.Name)}) require.NoError(t, err) require.True(t, cmp.Equal(userModel.UpdatedAt, userByName.UpdatedAt, dbTimeCmpOpt)) } diff --git a/models/wip.go b/models/wip.go index 6574951a..f408c973 100644 --- a/models/wip.go +++ b/models/wip.go @@ -5,6 +5,7 @@ import ( "time" "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/uptrace/bun" ) @@ -23,8 +24,8 @@ const ( type Stash struct { bun.BaseModel `bun:"table:stash"` ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` - CurrentTreeID uuid.UUID `bun:"current_tree_id,type:uuid,notnull"` - ParentTreeID uuid.UUID `bun:"parent_tree_id,type:uuid,notnull"` + CurrentTree hash.Hash `bun:"current_tree,type:bytea,notnull"` + ParentTree hash.Hash `bun:"parent_tree,type:bytea,notnull"` RepositoryID uuid.UUID `bun:"repository_id,type:uuid,notnull"` CreateID uuid.UUID `bun:"create_id,type:uuid,notnull"` CreatedAt time.Time `bun:"created_at"` @@ -40,6 +41,7 @@ type GetStashParam struct { type IStashRepo interface { Insert(ctx context.Context, repo *Stash) (*Stash, error) Get(ctx context.Context, params *GetStashParam) (*Stash, error) + UpdateCurrentHash(ctx context.Context, id uuid.UUID, newTreeHash hash.Hash) error } var _ IStashRepo = (*StashRepo)(nil) @@ -77,3 +79,13 @@ func (s *StashRepo) Get(ctx context.Context, params *GetStashParam) (*Stash, err } return repo, query.Scan(ctx, repo) } + +func (s *StashRepo) UpdateCurrentHash(ctx context.Context, id uuid.UUID, newTreeHash hash.Hash) error { + repo := &Stash{ + CurrentTree: newTreeHash, + } + _, err := s.db.NewUpdate().Model(repo).OmitZero().Column("current_tree"). + Where("id = ?", id). + Exec(ctx) + return err +} diff --git a/testhelper/pg.go b/testhelper/pg.go new file mode 100644 index 00000000..f50fc2ac --- /dev/null +++ b/testhelper/pg.go @@ -0,0 +1,43 @@ +package testhelper + +import ( + "context" + "fmt" + "os" + "testing" + + embeddedpostgres "github.com/fergusstrange/embedded-postgres" + "github.com/jiaozifs/jiaozifs/config" + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/models/migrations" + "github.com/phayes/freeport" + "github.com/stretchr/testify/require" + "github.com/uptrace/bun" + "go.uber.org/fx/fxtest" +) + +var TestConnTmpl = "postgres://postgres:postgres@localhost:%d/jiaozifs?sslmode=disable" + +func SetupDatabase(ctx context.Context, t *testing.T) (*embeddedpostgres.EmbeddedPostgres, string, *bun.DB) { + port, err := freeport.GetFreePort() + require.NoError(t, err) + tmpDir, err := os.MkdirTemp(os.TempDir(), "*") + require.NoError(t, err) + + cfg := embeddedpostgres.DefaultConfig(). + RuntimePath(tmpDir). + Port(uint32(port)). + Database("jiaozifs") + + postgres := embeddedpostgres.NewDatabase(cfg) + err = postgres.Start() + require.NoError(t, err) + + connStr := fmt.Sprintf(TestConnTmpl, port) + db, err := models.SetupDatabase(ctx, fxtest.NewLifecycle(t), &config.DatabaseConfig{Debug: true, Connection: connStr}) + require.NoError(t, err) + + err = migrations.MigrateDatabase(ctx, db) + require.NoError(t, err) + return postgres, connStr, db +} diff --git a/utils/hash/hash.go b/utils/hash/hash.go index f732352c..4e93226a 100644 --- a/utils/hash/hash.go +++ b/utils/hash/hash.go @@ -11,3 +11,10 @@ type Hash []byte func (hash Hash) Hex() string { return hex.EncodeToString(hash) } + +func (hash Hash) IsEmpty() bool { + if hash == nil { + return true + } + return len(hash) == 0 +} diff --git a/utils/hash/hashing_reader.go b/utils/hash/hashing_reader.go index d7a220dc..5eecee7b 100644 --- a/utils/hash/hashing_reader.go +++ b/utils/hash/hashing_reader.go @@ -3,14 +3,17 @@ package hash import ( "crypto/md5" //nolint:gosec "crypto/sha256" + "encoding/binary" "hash" "io" "strconv" ) +type HashType int //nolint + const ( - HashFunctionMD5 = iota - HashFunctionSHA256 + Md5 HashType = iota + SHA256 ) type Hasher struct { @@ -18,20 +21,20 @@ type Hasher struct { Sha256 hash.Hash } -func NewHasher(hashTypes ...int) *Hasher { +func NewHasher(hashTypes ...HashType) *Hasher { s := new(Hasher) for _, hashType := range hashTypes { switch hashType { - case HashFunctionMD5: + case Md5: if s.Md5 == nil { s.Md5 = md5.New() //nolint:gosec } - case HashFunctionSHA256: + case SHA256: if s.Sha256 == nil { s.Sha256 = sha256.New() } default: - panic("wrong hash type number " + strconv.Itoa(hashType)) + panic("wrong hash type number " + strconv.Itoa(int(hashType))) } } return s @@ -51,6 +54,49 @@ func (hasher *Hasher) Write(data []byte) (int, error) { return len(data), nil } +func (hasher *Hasher) WriteInt8(data int8) error { + _, err := hasher.Write([]byte{uint8(data)}) + return err +} + +func (hasher *Hasher) WriteUint8(data uint) error { + _, err := hasher.Write([]byte{byte(data)}) + return err +} + +func (hasher *Hasher) WriteString(data string) error { + _, err := hasher.Write([]byte(data)) + return err +} + +func (hasher *Hasher) WriteInt32(data int32) error { + buf := [4]byte{} + binary.BigEndian.PutUint32(buf[:], uint32(data)) + _, err := hasher.Write(buf[:]) + return err +} + +func (hasher *Hasher) WriteUint32(data uint32) error { + buf := [4]byte{} + binary.BigEndian.PutUint32(buf[:], data) + _, err := hasher.Write(buf[:]) + return err +} + +func (hasher *Hasher) WritInt64(data int64) error { + buf := [8]byte{} + binary.BigEndian.PutUint64(buf[:], uint64(data)) + _, err := hasher.Write(buf[:]) + return err +} + +func (hasher *Hasher) WritUint64(data uint64) error { + buf := [4]byte{} + binary.BigEndian.PutUint64(buf[:], data) + _, err := hasher.Write(buf[:]) + return err +} + type HashingReader struct { *Hasher originalReader io.Reader @@ -67,7 +113,7 @@ func (s *HashingReader) Read(p []byte) (int, error) { return nb, err } -func NewHashingReader(body io.Reader, hashTypes ...int) *HashingReader { +func NewHashingReader(body io.Reader, hashTypes ...HashType) *HashingReader { s := new(HashingReader) s.originalReader = body s.Hasher = NewHasher(hashTypes...) diff --git a/utils/hash/hashing_reader_test.go b/utils/hash/hashing_reader_test.go index 657c17c4..7bcbb496 100644 --- a/utils/hash/hashing_reader_test.go +++ b/utils/hash/hashing_reader_test.go @@ -5,26 +5,32 @@ import ( "testing" "github.com/stretchr/testify/require" + "github.com/tmthrgd/go-hex" ) func TestNewHasher(t *testing.T) { t.Run("single md5", func(t *testing.T) { - hasher := NewHasher(HashFunctionMD5) - hasher.Write([]byte{1, 2, 3, 4, 5}) + hasher := NewHasher(Md5) + _, err := hasher.Write([]byte{1, 2, 3, 4, 5}) + require.NoError(t, err) + md5Hash := hasher.Md5.Sum(nil) require.Equal(t, "7cfdd07889b3295d6a550914ab35e068", Hash(md5Hash).Hex()) }) t.Run("single sha256", func(t *testing.T) { - hasher := NewHasher(HashFunctionSHA256) - hasher.Write([]byte{1, 2, 3, 4, 5}) + hasher := NewHasher(SHA256) + _, err := hasher.Write([]byte{1, 2, 3, 4, 5}) + require.NoError(t, err) + sha256Hash := hasher.Sha256.Sum(nil) require.Equal(t, "74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0", Hash(sha256Hash).Hex()) }) t.Run("multi sha256", func(t *testing.T) { - hasher := NewHasher(HashFunctionMD5, HashFunctionSHA256) - hasher.Write([]byte{1, 2, 3, 4, 5}) + hasher := NewHasher(Md5, SHA256) + _, err := hasher.Write([]byte{1, 2, 3, 4, 5}) + require.NoError(t, err) md5Hash := hasher.Md5.Sum(nil) require.Equal(t, "7cfdd07889b3295d6a550914ab35e068", Hash(md5Hash).Hex()) @@ -36,30 +42,27 @@ func TestNewHasher(t *testing.T) { func TestHashingReader_Read(t *testing.T) { origData := []byte{1, 2, 3, 4, 5, 6, 7} - hasher := NewHashingReader(bytes.NewReader(origData), HashFunctionMD5, HashFunctionSHA256) + hashReader := NewHashingReader(bytes.NewReader(origData), Md5, SHA256) buf1 := make([]byte, 5) - wLen, err := hasher.Read(buf1) + wLen, err := hashReader.Read(buf1) require.NoError(t, err) require.Equal(t, 5, wLen) + require.Equal(t, origData[:5], buf1) - md5Hash := hasher.Md5.Sum(nil) - require.Equal(t, "7cfdd07889b3295d6a550914ab35e068", Hash(md5Hash).Hex()) - - sha256Hash := hasher.Sha256.Sum(nil) - require.Equal(t, "74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0", Hash(sha256Hash).Hex()) + md5Hash := hashReader.Md5.Sum(nil) + require.Equal(t, "7cfdd07889b3295d6a550914ab35e068", hex.EncodeToString(md5Hash)) + sha256Hash := hashReader.Sha256.Sum(nil) + require.Equal(t, "74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0", hex.EncodeToString(sha256Hash)) buf2 := make([]byte, 5) - wLen, err = hasher.Read(buf2) + wLen, err = hashReader.Read(buf2) require.NoError(t, err) require.Equal(t, 2, wLen) - - md5Hash = hasher.Md5.Sum(nil) - require.Equal(t, "498001217bc632cb158588224d7d23c4", Hash(md5Hash).Hex()) - - sha256Hash = hasher.Sha256.Sum(nil) - require.Equal(t, "32bbe378a25091502b2baf9f7258c19444e7a43ee4593b08030acd790bd66e6a", Hash(sha256Hash).Hex()) - - require.Equal(t, origData[:5], buf1) require.Equal(t, origData[5:], buf2[:2]) + + md5Hash = hashReader.Md5.Sum(nil) + require.Equal(t, "498001217bc632cb158588224d7d23c4", hex.EncodeToString(md5Hash)) + sha256Hash = hashReader.Sha256.Sum(nil) + require.Equal(t, "32bbe378a25091502b2baf9f7258c19444e7a43ee4593b08030acd790bd66e6a", hex.EncodeToString(sha256Hash)) } diff --git a/utils/httputil/tracing.go b/utils/httputil/tracing.go deleted file mode 100644 index 57640999..00000000 --- a/utils/httputil/tracing.go +++ /dev/null @@ -1,105 +0,0 @@ -package httputil - -import ( - "io" - "net/http" -) - -const ( - MaxBodyBytes = 750 // Log lines will be < 2KiB - RequestTracingMaxRequestBodySize = 1024 * 1024 * 50 // 50KB - RequestTracingMaxResponseBodySize = 1024 * 1024 * 150 // 150KB -) - -type CappedBuffer struct { - SizeBytes int - cursor int - Buffer []byte -} - -func (c *CappedBuffer) Write(p []byte) (n int, err error) { - // pretend to write the whole thing, but only write SizeBytes - if c.cursor >= c.SizeBytes { - return len(p), nil - } - if c.Buffer == nil { - c.Buffer = make([]byte, 0) - } - var written int - if len(p) > (c.SizeBytes - c.cursor) { - c.Buffer = append(c.Buffer, p[0:(c.SizeBytes-c.cursor)]...) - written = c.SizeBytes - c.cursor - } else { - c.Buffer = append(c.Buffer, p...) - written = len(p) - } - c.cursor += written - return len(p), nil -} - -type responseTracingWriter struct { - StatusCode int - ResponseSize int64 - BodyRecorder *CappedBuffer - - Writer http.ResponseWriter - multiWriter io.Writer -} - -func newResponseTracingWriter(w http.ResponseWriter, sizeInBytes int) *responseTracingWriter { - buf := &CappedBuffer{ - SizeBytes: sizeInBytes, - } - mw := io.MultiWriter(w, buf) - return &responseTracingWriter{ - StatusCode: http.StatusOK, - BodyRecorder: buf, - Writer: w, - multiWriter: mw, - } -} - -func (w *responseTracingWriter) Header() http.Header { - return w.Writer.Header() -} - -func (w *responseTracingWriter) Write(data []byte) (int, error) { - return w.multiWriter.Write(data) -} - -func (w *responseTracingWriter) WriteHeader(statusCode int) { - w.StatusCode = statusCode - w.Writer.WriteHeader(statusCode) -} - -type requestBodyTracer struct { - body io.ReadCloser - bodyRecorder *CappedBuffer - tee io.Reader -} - -func newRequestBodyTracer(body io.ReadCloser, sizeInBytes int) *requestBodyTracer { - w := &CappedBuffer{ - SizeBytes: sizeInBytes, - } - return &requestBodyTracer{ - body: body, - bodyRecorder: w, - tee: io.TeeReader(body, w), - } -} - -func (r *requestBodyTracer) Read(p []byte) (n int, err error) { - return r.tee.Read(p) -} - -func (r *requestBodyTracer) Close() error { - return r.body.Close() -} - -func presentBody(body []byte) string { - if len(body) > MaxBodyBytes { - body = body[:MaxBodyBytes] - } - return string(body) -} diff --git a/versionmgr/tree.go b/versionmgr/tree.go new file mode 100644 index 00000000..7b8585d5 --- /dev/null +++ b/versionmgr/tree.go @@ -0,0 +1,475 @@ +package versionmgr + +import ( + "context" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "time" + + "github.com/jiaozifs/jiaozifs/block" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/jiaozifs/jiaozifs/utils/pathutil" + + "golang.org/x/exp/slices" + + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/models/filemode" +) + +var EmptyRoot = &models.TreeNode{ + Hash: hash.Hash([]byte{}), + Type: models.TreeObject, +} + +var ( + ErrPathNotFound = fmt.Errorf("path not found") + ErrEntryExit = fmt.Errorf("entry exit") + ErrBlobMustBeLeaf = fmt.Errorf("blob must be leaf") + ErrNotDiretory = fmt.Errorf("path must be a directory") +) + +type TreeNodeWithNode struct { + Node *models.Object + Name string +} + +type TreeOp struct { + Object models.IObjectRepo +} + +func NewTreeOp(object models.IObjectRepo) *TreeOp { + return &TreeOp{ + Object: object, + } +} + +func (treeOp *TreeOp) WriteBlob(ctx context.Context, adapter block.Adapter, body io.Reader, contentLength int64, opts block.PutOpts) (*models.Blob, error) { + // handle the upload itself + hashReader := hash.NewHashingReader(body, hash.Md5) + tempf, err := os.CreateTemp("", "*") + if err != nil { + return nil, err + } + _, err = io.Copy(tempf, hashReader) + if err != nil { + return nil, err + } + + hash := hash.Hash(hashReader.Md5.Sum(nil)) + _, err = tempf.Seek(0, io.SeekStart) + if err != nil { + return nil, err + } + + defer func() { + name := tempf.Name() + _ = tempf.Close() + _ = os.RemoveAll(name) + }() + + address := pathutil.PathOfHash(hash) + err = adapter.Put(ctx, block.ObjectPointer{ + StorageNamespace: adapter.BlockstoreType() + "://", + IdentifierType: block.IdentifierTypeRelative, + Identifier: address, + }, contentLength, tempf, opts) + if err != nil { + return nil, err + } + + return &models.Blob{ + Hash: hash, + Size: hashReader.CopiedSize, + }, nil +} + +func (treeOp *TreeOp) SubDir(ctx context.Context, tn *models.TreeNode, name string) (*models.TreeNode, error) { + for _, node := range tn.SubObjects { + if node.Name == name { + if node.Mode == filemode.Dir { + return treeOp.Object.TreeNode(ctx, node.Hash) + } + return nil, fmt.Errorf("node is not directory") + } + } + return nil, ErrPathNotFound +} + +func (treeOp *TreeOp) SubFile(ctx context.Context, tn *models.TreeNode, name string) (*models.Blob, error) { + for _, node := range tn.SubObjects { + if node.Name == name { + if node.Mode == filemode.Regular || node.Mode == filemode.Executable { + return treeOp.Object.Blob(ctx, node.Hash) + } + return nil, fmt.Errorf("node is not blob") + } + } + return nil, ErrPathNotFound +} + +func (treeOp *TreeOp) SubEntry(_ context.Context, tn *models.TreeNode, name string) (models.TreeEntry, error) { + for _, node := range tn.SubObjects { + if node.Name == name { + return node, nil + } + } + return models.TreeEntry{}, ErrPathNotFound +} + +func (treeOp *TreeOp) AppendTreeEntry(ctx context.Context, tn *models.TreeNode, treeEntry models.TreeEntry) (*models.TreeNode, error) { + for _, node := range tn.SubObjects { + if node.Name == treeEntry.Name { + return nil, ErrEntryExit + } + } + + newTree := &models.TreeNode{ + Type: tn.Type, + SubObjects: tn.SubObjects, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + newTree.SubObjects = append(newTree.SubObjects, treeEntry) + hash, err := newTree.GetHash() + if err != nil { + return nil, err + } + newTree.Hash = hash + + obj, err := treeOp.Object.Insert(ctx, newTree.Object()) + if err != nil { + return nil, err + } + return obj.TreeNode(), nil +} + +func (treeOp *TreeOp) DeleteDirectObject(ctx context.Context, tn *models.TreeNode, name string) (*models.TreeNode, bool, error) { + newTree := &models.TreeNode{ + Type: tn.Type, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + for _, sub := range tn.SubObjects { + if sub.Name != name { //filter tree entry by name + newTree.SubObjects = append(newTree.SubObjects, sub) + } + } + + if len(newTree.SubObjects) == 0 { + //this node has no element return nothing + return nil, true, nil + } + + hash, err := newTree.GetHash() + if err != nil { + return nil, false, err + } + newTree.Hash = hash + + obj, err := treeOp.Object.Insert(ctx, newTree.Object()) + if err != nil { + return nil, false, err + } + return obj.TreeNode(), false, nil +} + +func (treeOp *TreeOp) ReplaceTreeEntry(ctx context.Context, tn *models.TreeNode, treeEntry models.TreeEntry) (*models.TreeNode, error) { + index := -1 + var sub models.TreeEntry + for index, sub = range tn.SubObjects { + if sub.Name == treeEntry.Name { + break + } + } + if index == -1 { + return nil, ErrPathNotFound + } + + newTree := &models.TreeNode{ + Type: tn.Type, + SubObjects: make([]models.TreeEntry, len(tn.SubObjects)), + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + copy(newTree.SubObjects, tn.SubObjects) + newTree.SubObjects[index] = treeEntry + + hash, err := newTree.GetHash() + if err != nil { + return nil, err + } + newTree.Hash = hash + + obj, err := treeOp.Object.Insert(ctx, newTree.Object()) + if err != nil { + return nil, err + } + return obj.TreeNode(), nil +} + +func (treeOp *TreeOp) MatchPath(ctx context.Context, tn *models.TreeNode, path string) ([]TreeNodeWithNode, []string, error) { + pathSegs := strings.Split(filepath.Clean(path), fmt.Sprintf("%c", os.PathSeparator)) + var existNodes []TreeNodeWithNode + var missingPath []string + //a/b/c/d/e + //a/b/c + //a/b/c/d/e/f/g + for index, seg := range pathSegs { + entry, err := treeOp.SubEntry(ctx, tn, seg) + if errors.Is(err, ErrPathNotFound) { + missingPath = pathSegs[index:] + return existNodes, missingPath, nil + } + + if entry.Mode == filemode.Dir { + tn, err = treeOp.SubDir(ctx, tn, entry.Name) + if err != nil { + return nil, nil, err + } + existNodes = append(existNodes, TreeNodeWithNode{ + Node: tn.Object(), + Name: entry.Name, + }) + } else { + //must be file + blob, err := treeOp.SubFile(ctx, tn, entry.Name) + if err != nil { + return nil, nil, err + } + existNodes = append(existNodes, TreeNodeWithNode{ + Node: blob.Object(), + Name: entry.Name, + }) + + if index != len(pathSegs)-1 { + //blob must be leaf + return nil, nil, ErrBlobMustBeLeaf + } + } + + } + return existNodes, nil, nil +} + +// AddLeaf insert new leaf in entry, if path not exit, create new +func (treeOp *TreeOp) AddLeaf(ctx context.Context, root *models.TreeNode, fullPath string, blob *models.Blob) (*models.TreeNode, error) { + existNode, missingPath, err := treeOp.MatchPath(ctx, root, fullPath) + if err != nil { + return nil, err + } + + if len(missingPath) == 0 { + return nil, ErrEntryExit + } + + _, err = treeOp.Object.Insert(ctx, blob.Object()) + if err != nil { + return nil, err + } + + slices.Reverse(missingPath) + var lastEntry models.TreeEntry + for index, path := range missingPath { + if index == 0 { + _, err = treeOp.Object.Insert(ctx, blob.Object()) + if err != nil { + return nil, err + } + lastEntry = models.TreeEntry{ + Name: path, + Mode: filemode.Regular, + Hash: blob.Hash, + } + continue + } + + newTree, err := models.NewTreeNode(lastEntry) + if err != nil { + return nil, err + } + _, err = treeOp.Object.Insert(ctx, newTree.Object()) + if err != nil { + return nil, err + } + lastEntry = models.TreeEntry{ + Name: path, + Mode: filemode.Dir, + Hash: newTree.Hash, + } + } + + slices.Reverse(existNode) + existNode = append(existNode, TreeNodeWithNode{ + Node: root.Object(), + Name: "", //root node have no name + }) + + var newNode *models.TreeNode + for index, node := range existNode { + if index == 0 { //insert new node + newNode, err = treeOp.AppendTreeEntry(ctx, node.Node.TreeNode(), lastEntry) + } else { //replace node + newNode, err = treeOp.ReplaceTreeEntry(ctx, node.Node.TreeNode(), lastEntry) + } + if err != nil { + return nil, err + } + lastEntry = models.TreeEntry{ + Name: node.Name, + Mode: filemode.Dir, + Hash: newNode.Hash, + } + } + return newNode, nil +} + +// ReplaceLeaf replace leaf with a new blob, all parent directory updated +func (treeOp *TreeOp) ReplaceLeaf(ctx context.Context, root *models.TreeNode, fullPath string, blob *models.Blob) (*models.TreeNode, error) { + existNode, missingPath, err := treeOp.MatchPath(ctx, root, fullPath) + if err != nil { + return nil, err + } + + if len(missingPath) > 0 { + return nil, ErrPathNotFound + } + + _, err = treeOp.Object.Insert(ctx, blob.Object()) + if err != nil { + return nil, err + } + + slices.Reverse(existNode) + existNode = append(existNode, TreeNodeWithNode{ + Node: root.Object(), + Name: "", //root node have no name + }) + + var lastEntry models.TreeEntry + var newNode *models.TreeNode + for index, node := range existNode { + if index == 0 { + lastEntry = models.TreeEntry{ + Name: node.Name, + Mode: filemode.Regular, + Hash: blob.Hash, + } + continue + } + + newNode, err = treeOp.ReplaceTreeEntry(ctx, node.Node.TreeNode(), lastEntry) + if err != nil { + return nil, err + } + lastEntry = models.TreeEntry{ + Name: node.Name, + Mode: filemode.Dir, + Hash: newNode.Hash, + } + } + return newNode, nil +} + +// RemoveEntry remove tree entry from specific tree, if directory have only one entry, this directory was remove too +// examples: a -> b +// a +// └── b +// +// ├── c.txt +// └── d.txt +// +// RemoveEntry(ctx, root, "a") return empty root +// RemoveEntry(ctx, root, "a/b/c.txt") return new root of(a/b/c.txt) +// RemoveEntry(ctx, root, "a/b") return empty root. a b c.txt d.txt all removed +func (treeOp *TreeOp) RemoveEntry(ctx context.Context, root *models.TreeNode, fullPath string) (*models.TreeNode, error) { + existNode, missingPath, err := treeOp.MatchPath(ctx, root, fullPath) + if err != nil { + return nil, err + } + + if len(missingPath) > 0 { + return nil, ErrPathNotFound + } + + slices.Reverse(existNode) + existNode = append(existNode, TreeNodeWithNode{ + Node: root.Object(), + Name: "", //root node have no name + }) + + lastEntry := models.TreeEntry{ + Name: existNode[0].Name, + Mode: filemode.Dir, + Hash: existNode[0].Node.Hash, + } + existNode = existNode[1:] + + var newNode *models.TreeNode + for index, node := range existNode { + if index == 0 || lastEntry.Hash.IsEmpty() { + var isEmpty bool + newNode, isEmpty, err = treeOp.DeleteDirectObject(ctx, node.Node.TreeNode(), lastEntry.Name) + if err != nil { + return nil, err + } + + lastEntry = models.TreeEntry{ + Name: node.Name, + Mode: filemode.Dir, + } + if !isEmpty { + lastEntry.Hash = newNode.Hash + } + } else { + newNode, err = treeOp.ReplaceTreeEntry(ctx, node.Node.TreeNode(), lastEntry) + if err != nil { + return nil, err + } + lastEntry = models.TreeEntry{ + Name: node.Name, + Mode: filemode.Dir, + Hash: newNode.Hash, + } + } + } + if newNode == nil { + return EmptyRoot, nil + } + return newNode, nil +} + +// Ls list tree entry of specific path of specific root +// examples: a -> b +// a +// └── b +// +// ├── c.txt +// └── d.txt +// +// Ls(ctx, root, "a") return b +// Ls(ctx, root, "a/b" return c.txt and d.txt +func (treeOp *TreeOp) Ls(ctx context.Context, root *models.TreeNode, fullPath string) ([]models.TreeEntry, error) { + if len(fullPath) == 0 { + return root.SubObjects, nil + } + + existNode, missingPath, err := treeOp.MatchPath(ctx, root, fullPath) + if err != nil { + return nil, err + } + + if len(missingPath) > 0 { + return nil, ErrPathNotFound + } + + lastNode := existNode[len(existNode)-1] + if lastNode.Node.Type != models.TreeObject { + return nil, ErrNotDiretory + } + + return lastNode.Node.SubObjects, nil +} diff --git a/versionmgr/tree_test.go b/versionmgr/tree_test.go new file mode 100644 index 00000000..d5f7a585 --- /dev/null +++ b/versionmgr/tree_test.go @@ -0,0 +1,159 @@ +package versionmgr + +import ( + "bytes" + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/jiaozifs/jiaozifs/block" + + "github.com/jiaozifs/jiaozifs/block/mem" + "github.com/jiaozifs/jiaozifs/models" + + "github.com/jiaozifs/jiaozifs/testhelper" +) + +func TestTreeOpWriteBlob(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + adapter := mem.New(ctx) + objRepo := models.NewObjectRepo(db) + + treeOp := NewTreeOp(objRepo) + + binary := []byte("Build simple, secure, scalable systems with Go") + bLen := int64(len(binary)) + r := bytes.NewReader(binary) + blob, err := treeOp.WriteBlob(ctx, adapter, r, bLen, block.PutOpts{}) + require.NoError(t, err) + assert.Equal(t, bLen, blob.Size) + assert.Equal(t, "f3b39786b86a96372589aa1166966643", blob.Hash.Hex()) +} + +func TestTreeOpTreeOp(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + adapter := mem.New(ctx) + objRepo := models.NewObjectRepo(db) + + treeOp := NewTreeOp(objRepo) + + binary := []byte("Build simple, secure, scalable systems with Go") + bLen := int64(len(binary)) + r := bytes.NewReader(binary) + blob, err := treeOp.WriteBlob(ctx, adapter, r, bLen, block.PutOpts{}) + require.NoError(t, err) + + oriRoot, err := treeOp.AddLeaf(ctx, EmptyRoot, "a/b/c.txt", blob) + require.NoError(t, err) + require.Equal(t, "3bf643c30934d121ee45d413b165f135", oriRoot.Hash.Hex()) + + //add again expect get an error + _, err = treeOp.AddLeaf(ctx, oriRoot, "a/b/c.txt", blob) + require.True(t, errors.Is(err, ErrEntryExit)) + + //update path + binary = []byte(`“At the time, no single team member knew Go, but within a month, everyone was writing in Go and we were building out the endpoints. ”`) + bLen = int64(len(binary)) + r = bytes.NewReader(binary) + blob, err = treeOp.WriteBlob(ctx, adapter, r, bLen, block.PutOpts{}) + require.NoError(t, err) + + updatedRoot, err := treeOp.ReplaceLeaf(ctx, oriRoot, "a/b/c.txt", blob) + require.NoError(t, err) + require.Equal(t, "8856b15f0f6c7ad21bfabe812df69e83", updatedRoot.Hash.Hex()) + + { + //add another branch + updatedRoot, err = treeOp.AddLeaf(ctx, updatedRoot, "a/b/d.txt", blob) + require.NoError(t, err) + require.Equal(t, "225f0ca6233681a441969922a7425db2", updatedRoot.Hash.Hex()) + + } + + { + //check fs structure + rootDir, err := objRepo.TreeNode(ctx, updatedRoot.Hash) + require.NoError(t, err) + require.Len(t, rootDir.SubObjects, 1) + require.Equal(t, "a", rootDir.SubObjects[0].Name) + + aDir, err := objRepo.TreeNode(ctx, rootDir.SubObjects[0].Hash) + require.NoError(t, err) + require.Len(t, aDir.SubObjects, 1) + require.Equal(t, "b", aDir.SubObjects[0].Name) + + bDir, err := objRepo.TreeNode(ctx, aDir.SubObjects[0].Hash) + require.NoError(t, err) + require.Len(t, bDir.SubObjects, 2) + require.Equal(t, "c.txt", bDir.SubObjects[0].Name) + require.Equal(t, "d.txt", bDir.SubObjects[1].Name) + } + + { + //check ls + subObjects, err := treeOp.Ls(ctx, updatedRoot, "a") + require.NoError(t, err) + require.Len(t, subObjects, 1) + require.Equal(t, "b", subObjects[0].Name) + + subObjects, err = treeOp.Ls(ctx, updatedRoot, "a/b") + require.NoError(t, err) + require.Len(t, subObjects, 2) + require.Equal(t, "c.txt", subObjects[0].Name) + require.Equal(t, "d.txt", subObjects[1].Name) + } + + rootAfterRemove, err := treeOp.RemoveEntry(ctx, updatedRoot, "a/b/c.txt") + require.NoError(t, err) + require.Equal(t, "f90e2d306ad172824fa171b9e0d9e133", rootAfterRemove.Hash.Hex()) + + rootAfterRemoveAll, err := treeOp.RemoveEntry(ctx, rootAfterRemove, "a/b/d.txt") + require.NoError(t, err) + require.Equal(t, "", rootAfterRemoveAll.Hash.Hex()) +} + +func TestRemoveEntry(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + adapter := mem.New(ctx) + objRepo := models.NewObjectRepo(db) + + treeOp := NewTreeOp(objRepo) + + binary := []byte("Build simple, secure, scalable systems with Go") + bLen := int64(len(binary)) + r := bytes.NewReader(binary) + blob, err := treeOp.WriteBlob(ctx, adapter, r, bLen, block.PutOpts{}) + require.NoError(t, err) + + root, err := treeOp.AddLeaf(ctx, EmptyRoot, "a/b/c.txt", blob) + require.NoError(t, err) + require.Equal(t, "3bf643c30934d121ee45d413b165f135", root.Hash.Hex()) + + //update path + binary = []byte(`“At the time, no single team member knew Go, but within a month, everyone was writing in Go and we were building out the endpoints. ”`) + bLen = int64(len(binary)) + r = bytes.NewReader(binary) + blob, err = treeOp.WriteBlob(ctx, adapter, r, bLen, block.PutOpts{}) + require.NoError(t, err) + + //add another branch + root, err = treeOp.AddLeaf(ctx, root, "a/b/d.txt", blob) + require.NoError(t, err) + require.Equal(t, "81173b4a85cc5643feacd38b975e61a1", root.Hash.Hex()) + + rootAfterRemoveAll, err := treeOp.RemoveEntry(ctx, root, "a/b") + require.NoError(t, err) + require.Equal(t, "", rootAfterRemoveAll.Hash.Hex()) +} From 4f0acd63ac6101eb30631cd22f4d781449abf6b6 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Fri, 8 Dec 2023 14:42:37 +0800 Subject: [PATCH 050/210] chore: rename stash to wip --- controller/object_ctl.go | 2 +- models/wip.go | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/controller/object_ctl.go b/controller/object_ctl.go index 9d6f2ddd..d098b9ba 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -33,7 +33,7 @@ type ObjectController struct { BlockAdapter block.Adapter - StashRepo models.StashRepo + StashRepo models.WipRepo UserRepo models.IUserRepo Repository models.RepositoryRepo Object models.ObjectRepo diff --git a/models/wip.go b/models/wip.go index f408c973..28a62224 100644 --- a/models/wip.go +++ b/models/wip.go @@ -21,8 +21,8 @@ const ( Modify ) -type Stash struct { - bun.BaseModel `bun:"table:stash"` +type WorkingInProcess struct { + bun.BaseModel `bun:"table:wip"` ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` CurrentTree hash.Hash `bun:"current_tree,type:bytea,notnull"` ParentTree hash.Hash `bun:"parent_tree,type:bytea,notnull"` @@ -38,23 +38,23 @@ type GetStashParam struct { RepositoryID uuid.UUID } -type IStashRepo interface { - Insert(ctx context.Context, repo *Stash) (*Stash, error) - Get(ctx context.Context, params *GetStashParam) (*Stash, error) +type IWipRepo interface { + Insert(ctx context.Context, repo *WorkingInProcess) (*WorkingInProcess, error) + Get(ctx context.Context, params *GetStashParam) (*WorkingInProcess, error) UpdateCurrentHash(ctx context.Context, id uuid.UUID, newTreeHash hash.Hash) error } -var _ IStashRepo = (*StashRepo)(nil) +var _ IWipRepo = (*WipRepo)(nil) -type StashRepo struct { +type WipRepo struct { db *bun.DB } -func NewStashRepo(db *bun.DB) IStashRepo { - return &StashRepo{db} +func NewWipRepo(db *bun.DB) IWipRepo { + return &WipRepo{db} } -func (s *StashRepo) Insert(ctx context.Context, repo *Stash) (*Stash, error) { +func (s *WipRepo) Insert(ctx context.Context, repo *WorkingInProcess) (*WorkingInProcess, error) { _, err := s.db.NewInsert().Model(repo).Exec(ctx) if err != nil { return nil, err @@ -62,8 +62,8 @@ func (s *StashRepo) Insert(ctx context.Context, repo *Stash) (*Stash, error) { return repo, nil } -func (s *StashRepo) Get(ctx context.Context, params *GetStashParam) (*Stash, error) { - repo := &Stash{} +func (s *WipRepo) Get(ctx context.Context, params *GetStashParam) (*WorkingInProcess, error) { + repo := &WorkingInProcess{} query := s.db.NewSelect().Model(repo) if uuid.Nil != params.ID { @@ -80,8 +80,8 @@ func (s *StashRepo) Get(ctx context.Context, params *GetStashParam) (*Stash, err return repo, query.Scan(ctx, repo) } -func (s *StashRepo) UpdateCurrentHash(ctx context.Context, id uuid.UUID, newTreeHash hash.Hash) error { - repo := &Stash{ +func (s *WipRepo) UpdateCurrentHash(ctx context.Context, id uuid.UUID, newTreeHash hash.Hash) error { + repo := &WorkingInProcess{ CurrentTree: newTreeHash, } _, err := s.db.NewUpdate().Model(repo).OmitZero().Column("current_tree"). From e1b38f303421c6b8c7ad88058429334ec4c5eb9e Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Fri, 8 Dec 2023 16:46:09 +0800 Subject: [PATCH 051/210] feat: support transaction --- cmd/daemon.go | 8 +- controller/object_ctl.go | 155 ++++++++++++++++++--------------------- controller/user_ctl.go | 14 ++-- models/object.go | 6 +- models/ref.go | 6 +- models/repo.go | 65 ++++++++++++++++ models/repo_test.go | 63 ++++++++++++++++ models/repository.go | 6 +- models/user.go | 7 +- models/wip.go | 12 +-- versionmgr/tree.go | 16 ++-- 11 files changed, 234 insertions(+), 124 deletions(-) create mode 100644 models/repo.go create mode 100644 models/repo_test.go diff --git a/cmd/daemon.go b/cmd/daemon.go index 425a0e4c..29bd6392 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -50,13 +50,9 @@ var daemonCmd = &cobra.Command{ //blockstore fx_opt.Override(new(block.Adapter), factory.BuildBlockAdapter), //database - fx_opt.Override(new(*bun.DB), models.SetupDatabase), + fx_opt.Override(new(bun.IDB), models.SetupDatabase), fx_opt.Override(fx_opt.NextInvoke(), migrations.MigrateDatabase), - fx_opt.Override(new(models.IUserRepo), models.NewUserRepo), - fx_opt.Override(new(models.IRepositoryRepo), models.NewRepositoryRepo), - fx_opt.Override(new(models.IRefRepo), models.NewRefRepo), - fx_opt.Override(new(models.IObjectRepo), models.NewObjectRepo), - + fx_opt.Override(new(models.IRepo), models.NewRepo), //api fx_opt.Override(fx_opt.NextInvoke(), apiImpl.SetupAPI), ) diff --git a/controller/object_ctl.go b/controller/object_ctl.go index d098b9ba..b6a2be7a 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -9,14 +9,13 @@ import ( "net/http" "time" + "github.com/go-openapi/swag" + "github.com/jiaozifs/jiaozifs/models/filemode" + "github.com/jiaozifs/jiaozifs/config" "github.com/jiaozifs/jiaozifs/versionmgr" - "github.com/jiaozifs/jiaozifs/models/filemode" - - "github.com/go-openapi/swag" - "github.com/jiaozifs/jiaozifs/block" "github.com/jiaozifs/jiaozifs/utils" @@ -33,11 +32,7 @@ type ObjectController struct { BlockAdapter block.Adapter - StashRepo models.WipRepo - UserRepo models.IUserRepo - Repository models.RepositoryRepo - Object models.ObjectRepo - Ref models.RefRepo + Repo models.IRepo Cfg config.BlockStoreConfig } @@ -53,13 +48,13 @@ func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsRespon } func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, userName string, repository string, params api.HeadObjectParams) { //nolint - user, err := oct.UserRepo.Get(ctx, &models.GetUserParam{Name: utils.String(userName)}) + user, err := oct.Repo.UserRepo().Get(ctx, &models.GetUserParam{Name: utils.String(userName)}) if err != nil { w.Error(err) return } - repo, err := oct.Repository.Get(ctx, &models.GetRepoParams{ + repo, err := oct.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ CreateID: user.ID, Name: utils.String(repository), }) @@ -68,7 +63,7 @@ func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsRespo return } - ref, err := oct.Ref.Get(ctx, &models.GetRefParams{ + ref, err := oct.Repo.RefRepo().Get(ctx, &models.GetRefParams{ RepositoryID: repo.ID, Name: utils.String(params.Branch), }) @@ -77,46 +72,44 @@ func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsRespo return } - commit, err := oct.Object.Commit(ctx, ref.CommitHash) + commit, err := oct.Repo.ObjectRepo().Commit(ctx, ref.CommitHash) if err != nil { w.Error(err) return } - treeOp := versionmgr.NewTreeOp(oct.Object) - - treeNode, err := oct.Object.TreeNode(ctx, commit.TreeHash) + objRepo := oct.Repo.ObjectRepo() + treeOp := versionmgr.NewTreeOp(oct.Repo.ObjectRepo()) + treeNode, err := objRepo.TreeNode(ctx, commit.TreeHash) if err != nil { w.Error(err) return } - existNodes, missingPath, err := treeOp.MatchPath(ctx, treeNode, params.Path) if err != nil { w.Error(err) return } - if len(missingPath) == 0 { w.Error(versionmgr.ErrPathNotFound) return } - blobWithName := existNodes[len(existNodes)-1] + objectWithName := existNodes[len(existNodes)-1] - blob, err := oct.Object.Blob(ctx, blobWithName.Node.Hash) + blob, err := objRepo.Blob(ctx, objectWithName.Node.Hash) if err != nil { w.Error(err) return } //lookup files - etag := httputil.ETag(blobWithName.Node.Hash.Hex()) + etag := httputil.ETag(objectWithName.Node.Hash.Hex()) w.Header().Set("ETag", etag) - lastModified := httputil.HeaderTimestamp(blobWithName.Node.CreatedAt) + lastModified := httputil.HeaderTimestamp(objectWithName.Node.CreatedAt) w.Header().Set("Last-Modified", lastModified) w.Header().Set("Accept-Ranges", "bytes") - w.Header().Set("Content-Type", httputil.ExtensionsByType(blobWithName.Name)) + w.Header().Set("Content-Type", httputil.ExtensionsByType(objectWithName.Name)) // for security, make sure the browser and any proxies en route don't cache the response w.Header().Set("Cache-Control", "no-store, must-revalidate") w.Header().Set("Expires", "0") @@ -144,16 +137,9 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes w.Error(err) return } - treeOp := versionmgr.TreeOp{Object: oct.Object} - var blob *models.Blob - if mediaType != "multipart/form-data" { - // handle non-multipart, direct content upload - blob, err = treeOp.WriteBlob(ctx, oct.BlockAdapter, r.Body, r.ContentLength, block.PutOpts{}) - if err != nil { - w.Error(err) - return - } - } else { + + reader := r.Body + if mediaType == "multipart/form-data" { // handle multipart upload boundary, ok := p["boundary"] if !ok { @@ -162,9 +148,9 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes } contentUploaded := false - reader := multipart.NewReader(r.Body, boundary) + partReader := multipart.NewReader(r.Body, boundary) for !contentUploaded { - part, err := reader.NextPart() + part, err := partReader.NextPart() if err == io.EOF { break } @@ -175,73 +161,74 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes contentType = part.Header.Get("Content-Type") partName := part.FormName() if partName == "content" { - // upload the first "content" and exit the loop - blob, err = treeOp.WriteBlob(ctx, oct.BlockAdapter, part, -1, block.PutOpts{}) - if err != nil { - _ = part.Close() - w.Error(err) - return - } + reader = part contentUploaded = true + } else { //close not target part + _ = part.Close() } - _ = part.Close() + } if !contentUploaded { w.Error(fmt.Errorf("multipart upload missing key 'content': %w", http.ErrMissingFile)) return } } + defer reader.Close() //nolint - user, err := oct.UserRepo.Get(ctx, &models.GetUserParam{Name: utils.String(userName)}) - if err != nil { - w.Error(err) - return - } + var response api.ObjectStats + err = oct.Repo.Transaction(ctx, func(dRepo models.IRepo) error { + treeOp := versionmgr.TreeOp{Object: dRepo.ObjectRepo()} + blob, err := treeOp.WriteBlob(ctx, oct.BlockAdapter, reader, r.ContentLength, block.PutOpts{}) + if err != nil { + return err + } - repo, err := oct.Repository.Get(ctx, &models.GetRepoParams{ - CreateID: user.ID, - Name: utils.String(repository), - }) - if err != nil { - w.Error(err) - return - } + user, err := dRepo.UserRepo().Get(ctx, &models.GetUserParam{Name: utils.String(userName)}) + if err != nil { + return err + } - stash, err := oct.StashRepo.Get(ctx, &models.GetStashParam{ - RepositoryID: repo.ID, - CreateID: user.ID, - }) - if err != nil { - w.Error(err) - return - } + repo, err := dRepo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ + CreateID: user.ID, + Name: utils.String(repository), + }) + if err != nil { + return err + } - workingTreeID, err := oct.Object.TreeNode(ctx, stash.CurrentTree) - if err != nil { - w.Error(err) - return - } + stash, err := dRepo.WipRepo().Get(ctx, &models.GetWipParam{ + RepositoryID: repo.ID, + CreateID: user.ID, + }) + if err != nil { + return err + } - newRoot, err := treeOp.AddLeaf(ctx, workingTreeID, params.Path, blob) - if err != nil { - w.Error(err) - return - } + workingTreeID, err := dRepo.ObjectRepo().TreeNode(ctx, stash.CurrentTree) + if err != nil { + return err + } + + newRoot, err := treeOp.AddLeaf(ctx, workingTreeID, params.Path, blob) + if err != nil { + return err + } + response = api.ObjectStats{ + Checksum: blob.Hash.Hex(), + Mtime: time.Now().Unix(), + Path: params.Path, + PathMode: utils.Uint32(uint32(filemode.Regular)), + SizeBytes: swag.Int64(blob.Size), + ContentType: &contentType, + Metadata: &api.ObjectUserMetadata{}, + } + return dRepo.WipRepo().UpdateCurrentHash(ctx, stash.ID, newRoot.Hash) + }) - err = oct.StashRepo.UpdateCurrentHash(ctx, stash.ID, newRoot.Hash) if err != nil { w.Error(err) return } - response := api.ObjectStats{ - Checksum: blob.Hash.Hex(), - Mtime: time.Now().Unix(), - Path: params.Path, - PathMode: utils.Uint32(uint32(filemode.Regular)), - SizeBytes: swag.Int64(blob.Size), - ContentType: &contentType, - Metadata: &api.ObjectUserMetadata{}, - } w.JSON(response) } diff --git a/controller/user_ctl.go b/controller/user_ctl.go index 9b0e7f7b..baa16b38 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -20,11 +20,11 @@ const ( type UserController struct { fx.In - Repo models.IUserRepo + Repo models.IRepo Config *config.Config } -func (A UserController) Login(ctx context.Context, w *api.JiaozifsResponse, r *http.Request) { +func (userCtl UserController) Login(ctx context.Context, w *api.JiaozifsResponse, r *http.Request) { // Decode requestBody var login auth.Login decoder := json.NewDecoder(r.Body) @@ -34,7 +34,7 @@ func (A UserController) Login(ctx context.Context, w *api.JiaozifsResponse, r *h } // perform login - authToken, err := login.Login(ctx, A.Repo, A.Config) + authToken, err := login.Login(ctx, userCtl.Repo.UserRepo(), userCtl.Config) if err != nil { w.Error(err) return @@ -42,7 +42,7 @@ func (A UserController) Login(ctx context.Context, w *api.JiaozifsResponse, r *h w.JSON(authToken) } -func (A UserController) Register(ctx context.Context, w *api.JiaozifsResponse, r *http.Request) { +func (userCtl UserController) Register(ctx context.Context, w *api.JiaozifsResponse, r *http.Request) { // Decode requestBody var register auth.Register decoder := json.NewDecoder(r.Body) @@ -51,7 +51,7 @@ func (A UserController) Register(ctx context.Context, w *api.JiaozifsResponse, r return } // perform register - err := register.Register(ctx, A.Repo) + err := register.Register(ctx, userCtl.Repo.UserRepo()) if err != nil { w.Error(err) return @@ -59,13 +59,13 @@ func (A UserController) Register(ctx context.Context, w *api.JiaozifsResponse, r w.OK() } -func (A UserController) GetUserInfo(ctx context.Context, w *api.JiaozifsResponse, r *http.Request) { +func (userCtl UserController) GetUserInfo(ctx context.Context, w *api.JiaozifsResponse, r *http.Request) { // Get token from Header tokenString := r.Header.Get(AuthHeader) userInfo := &auth.UserInfo{Token: tokenString} // perform GetUserInfo - usrInfo, err := userInfo.UserProfile(ctx, A.Repo, A.Config) + usrInfo, err := userInfo.UserProfile(ctx, userCtl.Repo.UserRepo(), userCtl.Config) if err != nil { w.Error(err) return diff --git a/models/object.go b/models/object.go index fbaef3c6..4a11bc77 100644 --- a/models/object.go +++ b/models/object.go @@ -295,11 +295,11 @@ type IObjectRepo interface { var _ IObjectRepo = (*ObjectRepo)(nil) type ObjectRepo struct { - db *bun.DB + db bun.IDB } -func NewObjectRepo(db *bun.DB) IObjectRepo { - return &ObjectRepo{db} +func NewObjectRepo(db bun.IDB) IObjectRepo { + return &ObjectRepo{db: db} } func (o ObjectRepo) Insert(ctx context.Context, obj *Object) (*Object, error) { diff --git a/models/ref.go b/models/ref.go index f7caffb2..8cc168a3 100644 --- a/models/ref.go +++ b/models/ref.go @@ -41,11 +41,11 @@ type IRefRepo interface { var _ IRefRepo = (*RefRepo)(nil) type RefRepo struct { - db *bun.DB + db bun.IDB } -func NewRefRepo(db *bun.DB) IRefRepo { - return &RefRepo{db} +func NewRefRepo(db bun.IDB) IRefRepo { + return &RefRepo{db: db} } func (r RefRepo) Insert(ctx context.Context, ref *Ref) (*Ref, error) { diff --git a/models/repo.go b/models/repo.go new file mode 100644 index 00000000..894bff59 --- /dev/null +++ b/models/repo.go @@ -0,0 +1,65 @@ +package models + +import ( + "context" + "database/sql" + + "github.com/uptrace/bun" +) + +type TxOption func(*sql.TxOptions) + +func IsolationLevelOption(level sql.IsolationLevel) TxOption { + return func(opts *sql.TxOptions) { + opts.Isolation = level + } +} + +type IRepo interface { + Transaction(ctx context.Context, fn func(repo IRepo) error, opts ...TxOption) error + UserRepo() IUserRepo + ObjectRepo() IObjectRepo + RefRepo() IRefRepo + RepositoryRepo() IRepositoryRepo + WipRepo() IWipRepo +} + +type PgRepo struct { + db bun.IDB +} + +func NewRepo(db bun.IDB) IRepo { + return &PgRepo{ + db: db, + } +} + +func (repo *PgRepo) Transaction(ctx context.Context, fn func(repo IRepo) error, opts ...TxOption) error { + sqlOpt := &sql.TxOptions{} + for _, opt := range opts { + opt(sqlOpt) + } + return repo.db.RunInTx(ctx, sqlOpt, func(ctx context.Context, tx bun.Tx) error { + return fn(NewRepo(tx)) + }) +} + +func (repo *PgRepo) UserRepo() IUserRepo { + return NewUserRepo(repo.db) +} + +func (repo *PgRepo) ObjectRepo() IObjectRepo { + return NewObjectRepo(repo.db) +} + +func (repo *PgRepo) RefRepo() IRefRepo { + return NewRefRepo(repo.db) +} + +func (repo *PgRepo) RepositoryRepo() IRepositoryRepo { + return NewRepositoryRepo(repo.db) +} + +func (repo *PgRepo) WipRepo() IWipRepo { + return NewWipRepo(repo.db) +} diff --git a/models/repo_test.go b/models/repo_test.go new file mode 100644 index 00000000..66ebb409 --- /dev/null +++ b/models/repo_test.go @@ -0,0 +1,63 @@ +package models_test + +import ( + "context" + "database/sql" + "errors" + "fmt" + "testing" + + "github.com/google/uuid" + + "github.com/brianvoe/gofakeit/v6" + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/stretchr/testify/require" +) + +func TestRepoTransaction(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + t.Run("simple", func(t *testing.T) { + pgRepo := models.NewRepo(db) + err := pgRepo.Transaction(ctx, func(repo models.IRepo) error { + userModel := &models.User{} + require.NoError(t, gofakeit.Struct(userModel)) + _, err := repo.UserRepo().Insert(ctx, userModel) + require.NoError(t, err) + return nil + }) + require.NoError(t, err) + }) + + t.Run("transaction", func(t *testing.T) { + pgRepo := models.NewRepo(db) + err := pgRepo.Transaction(ctx, func(repo models.IRepo) error { + object := &models.Object{} + require.NoError(t, gofakeit.Struct(object)) + _, err := repo.ObjectRepo().Insert(ctx, object) + require.NoError(t, err) + return err + }) + require.NoError(t, err) + }) + + t.Run("transaction rollback", func(t *testing.T) { + pgRepo := models.NewRepo(db) + var id uuid.UUID + err := pgRepo.Transaction(ctx, func(repo models.IRepo) error { + repositoryModel := &models.Repository{} + require.NoError(t, gofakeit.Struct(repositoryModel)) + insertedModel, err := repo.RepositoryRepo().Insert(ctx, repositoryModel) + require.NoError(t, err) + id = insertedModel.ID + return fmt.Errorf("rollback") + }) + require.Error(t, err) + + _, err = pgRepo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ID: id}) + require.True(t, errors.Is(err, sql.ErrNoRows)) + }) +} diff --git a/models/repository.go b/models/repository.go index d781d814..7f15f565 100644 --- a/models/repository.go +++ b/models/repository.go @@ -34,11 +34,11 @@ type IRepositoryRepo interface { var _ IRepositoryRepo = (*RepositoryRepo)(nil) type RepositoryRepo struct { - db *bun.DB + db bun.IDB } -func NewRepositoryRepo(db *bun.DB) IRepositoryRepo { - return &RepositoryRepo{db} +func NewRepositoryRepo(db bun.IDB) IRepositoryRepo { + return &RepositoryRepo{db: db} } func (r *RepositoryRepo) Insert(ctx context.Context, repo *Repository) (*Repository, error) { diff --git a/models/user.go b/models/user.go index 7b6ba929..df760a3f 100644 --- a/models/user.go +++ b/models/user.go @@ -2,7 +2,6 @@ package models import ( "context" - "time" "github.com/google/uuid" @@ -41,11 +40,11 @@ type IUserRepo interface { var _ IUserRepo = (*UserRepo)(nil) type UserRepo struct { - db *bun.DB + db bun.IDB } -func NewUserRepo(db *bun.DB) IUserRepo { - return &UserRepo{db} +func NewUserRepo(db bun.IDB) IUserRepo { + return &UserRepo{db: db} } func (userRepo *UserRepo) Get(ctx context.Context, params *GetUserParam) (*User, error) { diff --git a/models/wip.go b/models/wip.go index 28a62224..f3d283c7 100644 --- a/models/wip.go +++ b/models/wip.go @@ -32,7 +32,7 @@ type WorkingInProcess struct { UpdatedAt time.Time `bun:"updated_at"` } -type GetStashParam struct { +type GetWipParam struct { ID uuid.UUID CreateID uuid.UUID RepositoryID uuid.UUID @@ -40,18 +40,18 @@ type GetStashParam struct { type IWipRepo interface { Insert(ctx context.Context, repo *WorkingInProcess) (*WorkingInProcess, error) - Get(ctx context.Context, params *GetStashParam) (*WorkingInProcess, error) + Get(ctx context.Context, params *GetWipParam) (*WorkingInProcess, error) UpdateCurrentHash(ctx context.Context, id uuid.UUID, newTreeHash hash.Hash) error } var _ IWipRepo = (*WipRepo)(nil) type WipRepo struct { - db *bun.DB + db bun.IDB } -func NewWipRepo(db *bun.DB) IWipRepo { - return &WipRepo{db} +func NewWipRepo(db bun.IDB) IWipRepo { + return &WipRepo{db: db} } func (s *WipRepo) Insert(ctx context.Context, repo *WorkingInProcess) (*WorkingInProcess, error) { @@ -62,7 +62,7 @@ func (s *WipRepo) Insert(ctx context.Context, repo *WorkingInProcess) (*WorkingI return repo, nil } -func (s *WipRepo) Get(ctx context.Context, params *GetStashParam) (*WorkingInProcess, error) { +func (s *WipRepo) Get(ctx context.Context, params *GetWipParam) (*WorkingInProcess, error) { repo := &WorkingInProcess{} query := s.db.NewSelect().Model(repo) diff --git a/versionmgr/tree.go b/versionmgr/tree.go index 7b8585d5..7b6b7232 100644 --- a/versionmgr/tree.go +++ b/versionmgr/tree.go @@ -32,7 +32,7 @@ var ( ErrNotDiretory = fmt.Errorf("path must be a directory") ) -type TreeNodeWithNode struct { +type ObjectWithName struct { Node *models.Object Name string } @@ -211,9 +211,9 @@ func (treeOp *TreeOp) ReplaceTreeEntry(ctx context.Context, tn *models.TreeNode, return obj.TreeNode(), nil } -func (treeOp *TreeOp) MatchPath(ctx context.Context, tn *models.TreeNode, path string) ([]TreeNodeWithNode, []string, error) { +func (treeOp *TreeOp) MatchPath(ctx context.Context, tn *models.TreeNode, path string) ([]ObjectWithName, []string, error) { pathSegs := strings.Split(filepath.Clean(path), fmt.Sprintf("%c", os.PathSeparator)) - var existNodes []TreeNodeWithNode + var existNodes []ObjectWithName var missingPath []string //a/b/c/d/e //a/b/c @@ -230,7 +230,7 @@ func (treeOp *TreeOp) MatchPath(ctx context.Context, tn *models.TreeNode, path s if err != nil { return nil, nil, err } - existNodes = append(existNodes, TreeNodeWithNode{ + existNodes = append(existNodes, ObjectWithName{ Node: tn.Object(), Name: entry.Name, }) @@ -240,7 +240,7 @@ func (treeOp *TreeOp) MatchPath(ctx context.Context, tn *models.TreeNode, path s if err != nil { return nil, nil, err } - existNodes = append(existNodes, TreeNodeWithNode{ + existNodes = append(existNodes, ObjectWithName{ Node: blob.Object(), Name: entry.Name, }) @@ -303,7 +303,7 @@ func (treeOp *TreeOp) AddLeaf(ctx context.Context, root *models.TreeNode, fullPa } slices.Reverse(existNode) - existNode = append(existNode, TreeNodeWithNode{ + existNode = append(existNode, ObjectWithName{ Node: root.Object(), Name: "", //root node have no name }) @@ -344,7 +344,7 @@ func (treeOp *TreeOp) ReplaceLeaf(ctx context.Context, root *models.TreeNode, fu } slices.Reverse(existNode) - existNode = append(existNode, TreeNodeWithNode{ + existNode = append(existNode, ObjectWithName{ Node: root.Object(), Name: "", //root node have no name }) @@ -396,7 +396,7 @@ func (treeOp *TreeOp) RemoveEntry(ctx context.Context, root *models.TreeNode, fu } slices.Reverse(existNode) - existNode = append(existNode, TreeNodeWithNode{ + existNode = append(existNode, ObjectWithName{ Node: root.Object(), Name: "", //root node have no name }) From 4630c366b8ab340c715d15cc17a4a8e96b2a3f1f Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Fri, 8 Dec 2023 17:08:51 +0800 Subject: [PATCH 052/210] fix: daemon error --- cmd/daemon.go | 7 +++++-- config/default.go | 4 +++- controller/object_ctl.go | 4 ---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/cmd/daemon.go b/cmd/daemon.go index 29bd6392..bcd1b2c4 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -50,9 +50,12 @@ var daemonCmd = &cobra.Command{ //blockstore fx_opt.Override(new(block.Adapter), factory.BuildBlockAdapter), //database - fx_opt.Override(new(bun.IDB), models.SetupDatabase), + fx_opt.Override(new(*bun.DB), models.SetupDatabase), + fx_opt.Override(new(models.IRepo), func(db *bun.DB) models.IRepo { + return models.NewRepo(db) + }), + fx_opt.Override(fx_opt.NextInvoke(), migrations.MigrateDatabase), - fx_opt.Override(new(models.IRepo), models.NewRepo), //api fx_opt.Override(fx_opt.NextInvoke(), apiImpl.SetupAPI), ) diff --git a/config/default.go b/config/default.go index b1709816..bd678e01 100644 --- a/config/default.go +++ b/config/default.go @@ -1,5 +1,7 @@ package config +import "github.com/jiaozifs/jiaozifs/utils" + var DefaultLocalBSPath = "~/.jiaozifs/blockstore" var defaultCfg = Config{ @@ -12,7 +14,7 @@ var defaultCfg = Config{ }, Blockstore: BlockStoreConfig{ Type: "local", - DefaultNamespacePrefix: nil, + DefaultNamespacePrefix: utils.String("data"), Local: (*struct { Path string `mapstructure:"path"` ImportEnabled bool `mapstructure:"import_enabled"` diff --git a/controller/object_ctl.go b/controller/object_ctl.go index b6a2be7a..c1b4d54a 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -12,8 +12,6 @@ import ( "github.com/go-openapi/swag" "github.com/jiaozifs/jiaozifs/models/filemode" - "github.com/jiaozifs/jiaozifs/config" - "github.com/jiaozifs/jiaozifs/versionmgr" "github.com/jiaozifs/jiaozifs/block" @@ -33,8 +31,6 @@ type ObjectController struct { BlockAdapter block.Adapter Repo models.IRepo - - Cfg config.BlockStoreConfig } func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, user string, repository string, params api.DeleteObjectParams) { //nolint From 727a5bf6ff970c3e732c461e3dc7cc3d174f389e Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Sat, 9 Dec 2023 16:51:57 +0800 Subject: [PATCH 053/210] feat: add commit walk --- go.mod | 13 +- go.sum | 41 ++-- .../migrations/20210505110026_init_project.go | 7 + models/object.go | 65 +++++- models/ref.go | 7 +- models/ref_test.go | 17 +- models/user_test.go | 6 +- models/wip.go | 29 ++- models/wip_test.go | 46 ++++ utils/hash/hash.go | 33 +++ utils/hash/hash_test.go | 35 +++ versionmgr/commit.go | 171 ++++++++++++++ versionmgr/commit_node.go | 127 +++++++++++ versionmgr/commit_test.go | 147 ++++++++++++ versionmgr/commit_walker.go | 191 ++++++++++++++++ versionmgr/commit_walker_bfs.go | 99 ++++++++ versionmgr/commit_walker_bfs_filtered.go | 179 +++++++++++++++ versionmgr/merge_base.go | 211 ++++++++++++++++++ versionmgr/merge_base_test.go | 107 +++++++++ versionmgr/tree_node.go | 61 +++++ 20 files changed, 1546 insertions(+), 46 deletions(-) create mode 100644 models/wip_test.go create mode 100644 utils/hash/hash_test.go create mode 100644 versionmgr/commit.go create mode 100644 versionmgr/commit_node.go create mode 100644 versionmgr/commit_test.go create mode 100644 versionmgr/commit_walker.go create mode 100644 versionmgr/commit_walker_bfs.go create mode 100644 versionmgr/commit_walker_bfs_filtered.go create mode 100644 versionmgr/merge_base.go create mode 100644 versionmgr/merge_base_test.go create mode 100644 versionmgr/tree_node.go diff --git a/go.mod b/go.mod index c2177f4c..6275ebde 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,8 @@ require ( github.com/getkin/kin-openapi v0.118.0 github.com/go-chi/chi/v5 v5.0.10 github.com/go-chi/cors v1.2.1 + github.com/go-git/go-git v4.7.0+incompatible + github.com/go-git/go-git/v5 v5.10.1 github.com/go-openapi/swag v0.22.4 github.com/go-test/deep v1.1.0 github.com/golang-jwt/jwt v3.2.2+incompatible @@ -36,7 +38,6 @@ require ( github.com/oapi-codegen/runtime v1.1.0 github.com/ory/dockertest/v3 v3.10.0 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 - github.com/pjbgf/sha1cd v0.3.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.17.0 github.com/puzpuzpuz/xsync v1.5.2 @@ -44,6 +45,7 @@ require ( github.com/spf13/viper v1.17.0 github.com/stretchr/testify v1.8.4 github.com/thanhpk/randstr v1.0.6 + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc github.com/uptrace/bun v1.1.16 github.com/uptrace/bun/dialect/pgdialect v1.1.16 github.com/uptrace/bun/driver/pgdriver v1.1.16 @@ -85,14 +87,16 @@ require ( github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/containerd/continuity v0.3.0 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/docker/cli v23.0.6+incompatible // indirect github.com/docker/docker v23.0.6+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/emirpasic/gods v1.18.1 // indirect github.com/fatih/color v1.15.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -134,12 +138,13 @@ require ( github.com/opencontainers/runc v1.1.7 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/perimeterx/marshmallow v1.1.4 // indirect + github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.11.1 // indirect - github.com/rogpeppe/go-internal v1.10.0 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/rs/xid v1.5.0 // indirect github.com/sagikazarmark/locafero v0.3.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect @@ -149,7 +154,6 @@ require ( github.com/spf13/cast v1.5.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect @@ -175,6 +179,7 @@ require ( google.golang.org/grpc v1.58.3 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect mellium.im/sasl v0.3.1 // indirect ) diff --git a/go.sum b/go.sum index 644003fe..556980f7 100644 --- a/go.sum +++ b/go.sum @@ -120,23 +120,20 @@ github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -154,8 +151,6 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= -github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -175,10 +170,18 @@ github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git v4.7.0+incompatible h1:+W9rgGY4DOKKdX2x6HxSR7HNeTxqiKrOvKnuittYVdA= +github.com/go-git/go-git v4.7.0+incompatible/go.mod h1:6+421e08gnZWn30y26Vchf7efgYLe4dl5OQbBSUXShE= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git/v5 v5.10.1 h1:tu8/D8i+TWxgKpzQ3Vc43e+kkhXqtsZCKI/egajKnxk= +github.com/go-git/go-git/v5 v5.10.1/go.mod h1:uEuHjxkHap8kAl//V5F/nNWwqIYtP/402ddd05mp0wg= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= @@ -191,7 +194,6 @@ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9 github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= @@ -301,13 +303,11 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= @@ -355,7 +355,6 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -365,20 +364,17 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= -github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oapi-codegen/nethttp-middleware v1.0.1 h1:ZWvwfnMU0eloHX1VEJmQscQm3741t0vCm0eSIie1NIo= github.com/oapi-codegen/nethttp-middleware v1.0.1/go.mod h1:P7xtAvpoqNB+5obR9qRCeefH7YlXWSK3KgPs/9WB8tE= github.com/oapi-codegen/runtime v1.1.0 h1:rJpoNUawn5XTvekgfkvSZr0RqEnoYpFkyvrzfWeFKWM= github.com/oapi-codegen/runtime v1.1.0/go.mod h1:BeSfBkWWWnAnGdyS+S/GnlbmHKzf8/hwkvelJZDeKA8= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v1.1.7 h1:y2EZDS8sNng4Ksf0GUYNhKbTShZJPJg1FiXJNH/uoCk= github.com/opencontainers/runc v1.1.7/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50= -github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= @@ -412,8 +408,8 @@ github.com/puzpuzpuz/xsync v1.5.2 h1:yRAP4wqSOZG+/4pxJ08fPTwrfL0IzE/LKQ/cw509qGY github.com/puzpuzpuz/xsync v1.5.2/go.mod h1:K98BYhX3k1dQ2M63t1YNVDanbwUPmBCAhNmVrrxfiGg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -421,8 +417,6 @@ github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9c github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= @@ -453,7 +447,6 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/thanhpk/randstr v1.0.6 h1:psAOktJFD4vV9NEVb3qkhRSMvYh4ORRaj1+w/hn4B+o= github.com/thanhpk/randstr v1.0.6/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= @@ -470,9 +463,6 @@ github.com/uptrace/bun/driver/pgdriver v1.1.16 h1:b/NiSXk6Ldw7KLfMLbOqIkm4odHd7Q github.com/uptrace/bun/driver/pgdriver v1.1.16/go.mod h1:Rmfbc+7lx1z/umjMyAxkOHK81LgnGj71XC5YpA6k1vU= github.com/uptrace/bun/extra/bundebug v1.1.16 h1:SgicRQGtnjhrIhlYOxdkOm1Em4s6HykmT3JblHnoTBM= github.com/uptrace/bun/extra/bundebug v1.1.16/go.mod h1:SkiOkfUirBiO1Htc4s5bQKEq+JSeU1TkBVpMsPz2ePM= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= @@ -549,7 +539,6 @@ golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPI golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -852,6 +841,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/models/migrations/20210505110026_init_project.go b/models/migrations/20210505110026_init_project.go index 6980d971..24c8fe39 100644 --- a/models/migrations/20210505110026_init_project.go +++ b/models/migrations/20210505110026_init_project.go @@ -46,6 +46,13 @@ func init() { if err != nil { return err } + //wip + _, err = db.NewCreateTable(). + Model((*models.WorkingInProcess)(nil)). + Exec(ctx) + if err != nil { + return err + } //object _, err = db.NewCreateTable(). Model((*models.Object)(nil)). diff --git a/models/object.go b/models/object.go index 4a11bc77..88747b75 100644 --- a/models/object.go +++ b/models/object.go @@ -114,7 +114,7 @@ func (tn *TreeNode) GetHash() (hash.Hash, error) { } } - return hash.Hash(hasher.Md5.Sum(nil)), nil + return hasher.Md5.Sum(nil), nil } type Commit struct { @@ -133,7 +133,7 @@ type Commit struct { // Message is the commit/tag message, contains arbitrary text. Message string `bun:"message"` // TreeHash is the hash of the root tree of the commit. - TreeHash hash.Hash `bun:"tree+hash,type:bytea,notnull"` + TreeHash hash.Hash `bun:"tree_hash,type:bytea,notnull"` // ParentHashes are the hashes of the parent commits of the commit. ParentHashes []hash.Hash `bun:"parent_hashes,type:bytea[]"` @@ -141,6 +141,67 @@ type Commit struct { UpdatedAt time.Time `bun:"updated_at"` } +func (commit *Commit) GetHash() (hash.Hash, error) { + hasher := hash.NewHasher(hash.Md5) + err := hasher.WriteInt8(int8(commit.Type)) + if err != nil { + return nil, err + } + err = hasher.WriteString(commit.Author.Name) + if err != nil { + return nil, err + } + + err = hasher.WriteString(commit.Author.Email) + if err != nil { + return nil, err + } + + err = hasher.WritInt64(commit.Author.When.Unix()) + if err != nil { + return nil, err + } + + err = hasher.WriteString(commit.Committer.Name) + if err != nil { + return nil, err + } + + err = hasher.WriteString(commit.Committer.Email) + if err != nil { + return nil, err + } + + err = hasher.WritInt64(commit.Committer.When.Unix()) + if err != nil { + return nil, err + } + + err = hasher.WriteString(commit.MergeTag) + if err != nil { + return nil, err + } + + err = hasher.WriteString(commit.Message) + if err != nil { + return nil, err + } + + _, err = hasher.Write(commit.TreeHash) + for _, h := range commit.ParentHashes { + _, err = hasher.Write(h) + if err != nil { + return nil, err + } + } + + return hasher.Md5.Sum(nil), nil +} + +func (commit *Commit) NumParents() int { + return len(commit.ParentHashes) +} + func (commit *Commit) Object() *Object { return &Object{ Hash: commit.Hash, diff --git a/models/ref.go b/models/ref.go index 8cc168a3..da81f36b 100644 --- a/models/ref.go +++ b/models/ref.go @@ -35,6 +35,7 @@ type GetRefParams struct { type IRefRepo interface { Insert(ctx context.Context, repo *Ref) (*Ref, error) + UpdateCommitHash(ctx context.Context, id uuid.UUID, commitHash hash.Hash) error Get(ctx context.Context, id *GetRefParams) (*Ref, error) } @@ -65,7 +66,7 @@ func (r RefRepo) Get(ctx context.Context, params *GetRefParams) (*Ref, error) { } if uuid.Nil != params.RepositoryID { - query = query.Where("create_id = ?", params.RepositoryID) + query = query.Where("repository_id = ?", params.RepositoryID) } if params.Name != nil { @@ -73,5 +74,9 @@ func (r RefRepo) Get(ctx context.Context, params *GetRefParams) (*Ref, error) { } return repo, query.Scan(ctx, repo) +} +func (r RefRepo) UpdateCommitHash(ctx context.Context, id uuid.UUID, commitHash hash.Hash) error { + _, err := r.db.NewUpdate().Model((*Ref)(nil)).SetColumn("commit_hash", "?", commitHash).Where("id = ?", id).Exec(ctx) + return err } diff --git a/models/ref_test.go b/models/ref_test.go index 10a2c306..19bc93a3 100644 --- a/models/ref_test.go +++ b/models/ref_test.go @@ -4,6 +4,9 @@ import ( "context" "testing" + "github.com/jiaozifs/jiaozifs/utils" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" "github.com/google/uuid" @@ -26,9 +29,21 @@ func TestRefRepoInsert(t *testing.T) { require.NotEqual(t, uuid.Nil, newRef.ID) ref, err := repo.Get(ctx, &models.GetRefParams{ - ID: newRef.ID, + ID: newRef.ID, + RepositoryID: newRef.RepositoryID, + Name: utils.String(newRef.Name), }) require.NoError(t, err) require.True(t, cmp.Equal(refModel, ref, dbTimeCmpOpt)) + + mockHash := hash.Hash("mock hash") + err = repo.UpdateCommitHash(ctx, ref.ID, mockHash) + require.NoError(t, err) + + refAfterUpdated, err := repo.Get(ctx, &models.GetRefParams{ + ID: newRef.ID, + }) + require.NoError(t, err) + require.Equal(t, mockHash, refAfterUpdated.CommitHash) } diff --git a/models/user_test.go b/models/user_test.go index 9778ccfc..1817c3b5 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -40,7 +40,7 @@ func TestNewUserRepo(t *testing.T) { user, err := repo.Get(ctx, &models.GetUserParam{ID: newUser.ID}) require.NoError(t, err) - require.True(t, cmp.Equal(userModel.UpdatedAt, user.UpdatedAt, dbTimeCmpOpt)) + require.True(t, cmp.Equal(userModel, user, dbTimeCmpOpt)) ep, err := repo.GetEPByName(ctx, newUser.Name) require.NoError(t, err) @@ -48,9 +48,9 @@ func TestNewUserRepo(t *testing.T) { userByEmail, err := repo.Get(ctx, &models.GetUserParam{Email: utils.String(newUser.Email)}) require.NoError(t, err) - require.True(t, cmp.Equal(userModel.UpdatedAt, userByEmail.UpdatedAt, dbTimeCmpOpt)) + require.True(t, cmp.Equal(userModel, userByEmail, dbTimeCmpOpt)) userByName, err := repo.Get(ctx, &models.GetUserParam{Name: utils.String(newUser.Name)}) require.NoError(t, err) - require.True(t, cmp.Equal(userModel.UpdatedAt, userByName.UpdatedAt, dbTimeCmpOpt)) + require.True(t, cmp.Equal(userModel, userByName, dbTimeCmpOpt)) } diff --git a/models/wip.go b/models/wip.go index f3d283c7..d3836d33 100644 --- a/models/wip.go +++ b/models/wip.go @@ -9,16 +9,11 @@ import ( "github.com/uptrace/bun" ) -// Action values represent the kind of things a Change can represent: -// insertion, deletions or modifications of files. -type Action int +type WipState int -// The set of possible actions in a change. const ( - _ Action = iota - Insert - Delete - Modify + Init WipState = iota + Completed ) type WorkingInProcess struct { @@ -26,7 +21,9 @@ type WorkingInProcess struct { ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` CurrentTree hash.Hash `bun:"current_tree,type:bytea,notnull"` ParentTree hash.Hash `bun:"parent_tree,type:bytea,notnull"` + RefID uuid.UUID `bun:"ref_id,type:uuid,notnull"` RepositoryID uuid.UUID `bun:"repository_id,type:uuid,notnull"` + State WipState `bun:"state"` CreateID uuid.UUID `bun:"create_id,type:uuid,notnull"` CreatedAt time.Time `bun:"created_at"` UpdatedAt time.Time `bun:"updated_at"` @@ -42,6 +39,7 @@ type IWipRepo interface { Insert(ctx context.Context, repo *WorkingInProcess) (*WorkingInProcess, error) Get(ctx context.Context, params *GetWipParam) (*WorkingInProcess, error) UpdateCurrentHash(ctx context.Context, id uuid.UUID, newTreeHash hash.Hash) error + UpdateState(ctx context.Context, id uuid.UUID, state WipState) error } var _ IWipRepo = (*WipRepo)(nil) @@ -81,10 +79,21 @@ func (s *WipRepo) Get(ctx context.Context, params *GetWipParam) (*WorkingInProce } func (s *WipRepo) UpdateCurrentHash(ctx context.Context, id uuid.UUID, newTreeHash hash.Hash) error { - repo := &WorkingInProcess{ + wip := &WorkingInProcess{ CurrentTree: newTreeHash, } - _, err := s.db.NewUpdate().Model(repo).OmitZero().Column("current_tree"). + _, err := s.db.NewUpdate().Model(wip).OmitZero().Column("current_tree"). + Where("id = ?", id). + Exec(ctx) + return err +} + +func (s *WipRepo) UpdateState(ctx context.Context, id uuid.UUID, state WipState) error { + wip := &WorkingInProcess{ + State: state, + } + + _, err := s.db.NewUpdate().Model(wip).OmitZero().Column("state"). Where("id = ?", id). Exec(ctx) return err diff --git a/models/wip_test.go b/models/wip_test.go new file mode 100644 index 00000000..81bebf9a --- /dev/null +++ b/models/wip_test.go @@ -0,0 +1,46 @@ +package models_test + +import ( + "context" + "testing" + + "github.com/jiaozifs/jiaozifs/utils/hash" + + "github.com/brianvoe/gofakeit/v6" + "github.com/google/go-cmp/cmp" + "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/stretchr/testify/require" +) + +func TestWipRepo(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + repo := models.NewWipRepo(db) + + wipModel := &models.WorkingInProcess{} + require.NoError(t, gofakeit.Struct(wipModel)) + newWipModel, err := repo.Insert(ctx, wipModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newWipModel.ID) + + user, err := repo.Get(ctx, &models.GetWipParam{ID: newWipModel.ID}) + require.NoError(t, err) + + require.True(t, cmp.Equal(newWipModel, user, dbTimeCmpOpt)) + + err = repo.UpdateCurrentHash(ctx, newWipModel.ID, hash.Hash("mock hash")) + require.NoError(t, err) + updatedUser, err := repo.Get(ctx, &models.GetWipParam{ID: newWipModel.ID}) + require.NoError(t, err) + require.Equal(t, "mock hash", string(updatedUser.CurrentTree)) + + err = repo.UpdateState(ctx, newWipModel.ID, models.Completed) + require.NoError(t, err) + updatedUser, err = repo.Get(ctx, &models.GetWipParam{ID: newWipModel.ID}) + require.NoError(t, err) + require.Equal(t, models.Completed, updatedUser.State) +} diff --git a/utils/hash/hash.go b/utils/hash/hash.go index 4e93226a..dee94781 100644 --- a/utils/hash/hash.go +++ b/utils/hash/hash.go @@ -3,9 +3,15 @@ package hash import ( + "encoding/json" + "fmt" + "github.com/tmthrgd/go-hex" ) +var _ json.Marshaler = (*Hash)(nil) +var _ json.Unmarshaler = (*Hash)(nil) + type Hash []byte func (hash Hash) Hex() string { @@ -18,3 +24,30 @@ func (hash Hash) IsEmpty() bool { } return len(hash) == 0 } + +func (hash *Hash) UnmarshalJSON(bytes []byte) error { + if len(bytes) < 2 { + return fmt.Errorf("hash json must be string") + } + if bytes[0] != '"' || bytes[len(bytes)-1] != '"' { + return fmt.Errorf("hash json must be string") + } + + if len(bytes) == 2 { + return nil + } + + hexData, err := hex.DecodeString(string(bytes[1 : len(bytes)-1])) + if err != nil { + return err + } + *hash = hexData + return nil +} + +func (hash Hash) MarshalJSON() ([]byte, error) { + if hash == nil { + return []byte(`""`), nil + } + return []byte(`"` + hash.Hex() + `"`), nil +} diff --git a/utils/hash/hash_test.go b/utils/hash/hash_test.go new file mode 100644 index 00000000..496de5d8 --- /dev/null +++ b/utils/hash/hash_test.go @@ -0,0 +1,35 @@ +package hash + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestHashJSON(t *testing.T) { + type A struct { + H Hash + } + + t.Run("success", func(t *testing.T) { + data, err := json.Marshal(A{H: Hash("aaaa")}) + require.NoError(t, err) + require.Equal(t, "{\"H\":\"61616161\"}", string(data)) + + a := A{} + err = json.Unmarshal(data, &a) + require.NoError(t, err) + require.Equal(t, "aaaa", string(a.H)) + }) + t.Run("null", func(t *testing.T) { + data, err := json.Marshal(A{}) + require.NoError(t, err) + require.Equal(t, "{\"H\":\"\"}", string(data)) + + a := A{} + err = json.Unmarshal(data, &a) + require.NoError(t, err) + require.Equal(t, "", string(a.H)) + }) +} diff --git a/versionmgr/commit.go b/versionmgr/commit.go new file mode 100644 index 00000000..544c1088 --- /dev/null +++ b/versionmgr/commit.go @@ -0,0 +1,171 @@ +package versionmgr + +import ( + "bytes" + "context" + "time" + + "github.com/go-git/go-git/v5/utils/merkletrie" + + "github.com/jiaozifs/jiaozifs/models/filemode" + + "github.com/go-git/go-git/v5/utils/merkletrie/noder" + + "github.com/jiaozifs/jiaozifs/utils/hash" + + "github.com/google/uuid" + + "github.com/jiaozifs/jiaozifs/models" +) + +type CommitOp struct { + User models.IUserRepo + Object models.IObjectRepo + Wip models.IWipRepo + Ref models.IRefRepo +} + +func NewCommitOp(repo models.IRepo) *CommitOp { + return &CommitOp{ + User: repo.UserRepo(), + Object: repo.ObjectRepo(), + Wip: repo.WipRepo(), + Ref: repo.RefRepo(), + } +} + +func (commitOp *CommitOp) AddCommit(ctx context.Context, refID, committerID, wipID uuid.UUID, msg string) (*models.Commit, error) { + wip, err := commitOp.Wip.Get(ctx, &models.GetWipParam{ + ID: wipID, + }) + if err != nil { + return nil, err + } + + committer, err := commitOp.User.Get(ctx, &models.GetUserParam{ + ID: committerID, + }) + if err != nil { + return nil, err + } + + creator, err := commitOp.User.Get(ctx, &models.GetUserParam{ + ID: wip.CreateID, + }) + if err != nil { + return nil, err + } + + ref, err := commitOp.Ref.Get(ctx, &models.GetRefParams{ + ID: refID, + }) + if err != nil { + return nil, err + } + + commit := &models.Commit{ + Hash: nil, + Type: models.CommitObject, + Author: models.Signature{ + Name: creator.Name, + Email: creator.Email, + When: wip.UpdatedAt, + }, + Committer: models.Signature{ + Name: committer.Name, + Email: committer.Email, + When: time.Now(), + }, + MergeTag: "", + Message: msg, + TreeHash: wip.CurrentTree, + ParentHashes: []hash.Hash{ref.CommitHash}, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + commitHash, err := commit.GetHash() + if err != nil { + return nil, err + } + commit.Hash = commitHash + _, err = commitOp.Object.Insert(ctx, commit.Object()) + if err != nil { + return nil, err + } + + ref.CommitHash = commitHash + err = commitOp.Ref.UpdateCommitHash(ctx, ref.ID, commitHash) + if err != nil { + return nil, err + } + err = commitOp.Wip.UpdateState(ctx, wipID, models.Completed) + if err != nil { + return nil, err + } + return commit, nil +} + +func (commitOp *CommitOp) DiffCommit(ctx context.Context, baseCommitID, toCommitID hash.Hash) (merkletrie.Changes, error) { + baseCommit, err := commitOp.Object.Commit(ctx, baseCommitID) + if err != nil { + return nil, err + } + + toCommit, err := commitOp.Object.Commit(ctx, toCommitID) + if err != nil { + return nil, err + } + + fromNode := &TreeNode{ + Ctx: ctx, + TreeEntry: models.TreeEntry{ + Name: "", + Mode: filemode.Dir, + Hash: baseCommit.TreeHash, + }, + Object: commitOp.Object, + } + + toNode := &TreeNode{ + Ctx: ctx, + TreeEntry: models.TreeEntry{ + Name: "", + Mode: filemode.Dir, + Hash: toCommit.TreeHash, + }, + Object: commitOp.Object, + } + return merkletrie.DiffTreeContext(ctx, fromNode, toNode, func(a, b noder.Hasher) bool { + return bytes.Equal(a.Hash(), b.Hash()) + }) +} + +func (commitOp *CommitOp) Merge(ctx context.Context, mergerID, baseRefID, mergeRefID uuid.UUID) (*models.Commit, error) { + _, err := commitOp.User.Get(ctx, &models.GetUserParam{ + ID: mergerID, + }) + if err != nil { + return nil, err + } + + baseRef, err := commitOp.Ref.Get(ctx, &models.GetRefParams{ + ID: baseRefID, + }) + if err != nil { + return nil, err + } + + mergeRef, err := commitOp.Ref.Get(ctx, &models.GetRefParams{ + ID: mergeRefID, + }) + if err != nil { + return nil, err + } + + _, err = commitOp.DiffCommit(ctx, baseRef.CommitHash, mergeRef.CommitHash) + if err != nil { + return nil, err + } + + return nil, nil +} diff --git a/versionmgr/commit_node.go b/versionmgr/commit_node.go new file mode 100644 index 00000000..9b796a43 --- /dev/null +++ b/versionmgr/commit_node.go @@ -0,0 +1,127 @@ +package versionmgr + +import ( + "context" + "errors" + "io" + + "github.com/go-git/go-git/v5/plumbing/storer" + "github.com/jiaozifs/jiaozifs/utils/hash" + + "github.com/jiaozifs/jiaozifs/models/filemode" + + "github.com/jiaozifs/jiaozifs/models" +) + +var ( + ErrStop = errors.New("stop iter") +) + +type CommitNode struct { + Ctx context.Context + models.Commit + Object models.IObjectRepo +} + +// Tree returns the Tree from the commit. +func (c *CommitNode) Tree() (*TreeNode, error) { + treeNode, err := c.Object.TreeNode(c.Ctx, c.TreeHash) + if err != nil { + return nil, err + } + return &TreeNode{ + Ctx: c.Ctx, + TreeEntry: models.TreeEntry{ + Name: "", + Mode: filemode.Dir, + Hash: treeNode.Hash, + }, + Object: c.Object, + }, nil +} + +// Parents return a CommitIter to the parent Commits. +func (c *CommitNode) Parents() ([]*CommitNode, error) { + parentNodes := make([]*CommitNode, len(c.ParentHashes)) + for _, hash := range c.ParentHashes { + commit, err := c.Object.Commit(c.Ctx, hash) + if err != nil { + return nil, err + } + parentNodes = append(parentNodes, &CommitNode{ + Ctx: c.Ctx, + Commit: *commit, + Object: c.Object, + }) + } + return parentNodes, nil +} + +func (c *CommitNode) GetCommit(hash hash.Hash) (*CommitNode, error) { + commit, err := c.Object.Commit(c.Ctx, hash) + if err != nil { + return nil, err + } + return &CommitNode{ + Ctx: c.Ctx, + Commit: *commit, + Object: c.Object, + }, nil +} + +func (c *CommitNode) GetCommits(hashes []hash.Hash) ([]*CommitNode, error) { + commits := make([]*CommitNode, len(hashes)) + for i, hash := range hashes { + commit, err := c.Object.Commit(c.Ctx, hash) + if err != nil { + return nil, err + } + commits[i] = &CommitNode{ + Ctx: c.Ctx, + Commit: *commit, + Object: c.Object, + } + } + return commits, nil +} + +// CommitIter is a generic closable interface for iterating over commits. +type CommitIter interface { + Next() (*CommitNode, error) + ForEach(func(*CommitNode) error) error +} + +var _ CommitIter = (*arraryCommitIter)(nil) + +type arraryCommitIter struct { + commits []*CommitNode + idx int +} + +func newArrayCommitIter(commits []*CommitNode) *arraryCommitIter { + return &arraryCommitIter{ + commits: commits, + idx: -1, + } +} + +func (a arraryCommitIter) Next() (*CommitNode, error) { + if a.idx == len(a.commits)-1 { + a.idx++ + return a.commits[a.idx], nil + } + return nil, io.EOF +} + +func (a arraryCommitIter) ForEach(f func(*CommitNode) error) error { + for _, commit := range a.commits { + err := f(commit) + if errors.Is(err, storer.ErrStop) { + break + } + if err != nil { + return err + } + } + return nil +} diff --git a/versionmgr/commit_test.go b/versionmgr/commit_test.go new file mode 100644 index 00000000..6e12ae7a --- /dev/null +++ b/versionmgr/commit_test.go @@ -0,0 +1,147 @@ +package versionmgr + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/google/uuid" + + "github.com/stretchr/testify/require" + + "github.com/jiaozifs/jiaozifs/utils/hash" + + "github.com/jiaozifs/jiaozifs/models" + + "github.com/jiaozifs/jiaozifs/testhelper" +) + +func TestCommitOp_DiffCommit(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + repo := models.NewRepo(db) + //commit1 a.txt b/c.txt b/e.txt + //commit2 a.txt b/d.txt b/e.txt + testData1 := ` +a.txt |a +b/c.txt |c +b/e.txt |e1 +` + testData2 := ` +a.txt |a +b/d.txt |d +b/e.txt |e2 +` + root1, err := makeRoot(ctx, repo.ObjectRepo(), testData1) + require.NoError(t, err) + root2, err := makeRoot(ctx, repo.ObjectRepo(), testData2) + require.NoError(t, err) + op := NewCommitOp(repo) + + user, err := makeUser(ctx, repo.UserRepo(), "admin") + require.NoError(t, err) + + project, err := makeRepository(ctx, repo.RepositoryRepo(), "testproject") + require.NoError(t, err) + + baseRef, err := makeRef(ctx, repo.RefRepo(), "feat/base", project.ID, hash.Hash("a")) + require.NoError(t, err) + baseWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseRef.ID, root1.Hash) + require.NoError(t, err) + baseCommit, err := op.AddCommit(ctx, baseRef.ID, user.ID, baseWip.ID, "base commit") + require.NoError(t, err) + + mergeRef, err := makeRef(ctx, repo.RefRepo(), "feat/merge", project.ID, hash.Hash("a")) + require.NoError(t, err) + mergeWip, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeRef.ID, root2.Hash) + require.NoError(t, err) + mergeCommit, err := op.AddCommit(ctx, mergeRef.ID, user.ID, mergeWip.ID, "merge commit") + require.NoError(t, err) + + changes, err := op.DiffCommit(ctx, baseCommit.Hash, mergeCommit.Hash) + require.NoError(t, err) + require.Len(t, changes, 3) +} + +func makeUser(ctx context.Context, userRepo models.IUserRepo, name string) (*models.User, error) { + user := &models.User{ + Name: "name", + Email: "xxx@gg.com", + EncryptedPassword: "123", + CurrentSignInAt: time.Time{}, + LastSignInAt: time.Time{}, + CurrentSignInIP: "", + LastSignInIP: "", + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + } + return userRepo.Insert(ctx, user) +} + +func makeRepository(ctx context.Context, repoRepo models.IRepositoryRepo, name string) (*models.Repository, error) { + user := &models.Repository{ + Name: name, + Description: "", + HEAD: "main", + CreateID: uuid.UUID{}, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + } + return repoRepo.Insert(ctx, user) +} + +func makeRef(ctx context.Context, refRepo models.IRefRepo, name string, repoID uuid.UUID, commitHash hash.Hash) (*models.Ref, error) { + ref := &models.Ref{ + RepositoryID: repoID, + CommitHash: commitHash, + Name: name, + Description: "", + CreateID: uuid.UUID{}, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + } + return refRepo.Insert(ctx, ref) +} + +func makeWip(ctx context.Context, wipRepo models.IWipRepo, repoID, refID uuid.UUID, curHash hash.Hash) (*models.WorkingInProcess, error) { + wip := &models.WorkingInProcess{ + CurrentTree: curHash, + ParentTree: hash.Hash("mock"), + RefID: refID, + RepositoryID: repoID, + CreateID: uuid.UUID{}, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + } + return wipRepo.Insert(ctx, wip) +} +func makeRoot(ctx context.Context, objRepo models.IObjectRepo, testData string) (*models.TreeNode, error) { + lines := strings.Split(testData, "\n") + treeOp := NewTreeOp(objRepo) + root := EmptyRoot + var err error + for _, line := range lines { + if len(strings.TrimSpace(line)) == 0 { + continue + } + commitData := strings.Split(strings.TrimSpace(line), "|") + fullPath := strings.TrimSpace(commitData[0]) + fileHash := strings.TrimSpace(commitData[1]) + blob := &models.Blob{ + Hash: hash.Hash(fileHash), + Type: models.BlobObject, + Size: 10, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + root, err = treeOp.AddLeaf(ctx, root, fullPath, blob) + if err != nil { + return nil, err + } + } + return root, nil +} diff --git a/versionmgr/commit_walker.go b/versionmgr/commit_walker.go new file mode 100644 index 00000000..6646c4af --- /dev/null +++ b/versionmgr/commit_walker.go @@ -0,0 +1,191 @@ +package versionmgr + +import ( + "errors" + "io" + + "github.com/jiaozifs/jiaozifs/utils/hash" + + "github.com/go-git/go-git/v5/plumbing/storer" +) + +type commitPreIterator struct { + seenExternal map[string]bool + seen map[string]bool + stack []CommitIter + start *CommitNode +} + +// NewCommitPreorderIter returns a CommitIter that walks the commit history, +// starting at the given commit and visiting its parents in pre-order. +// The given callback will be called for each visited commit. Each commit will +// be visited only once. If the callback returns an error, walking will stop +// and will return the error. Other errors might be returned if the history +// cannot be traversed (e.g. missing objects). Ignore allows to skip some +// commits from being iterated. +func NewCommitPreorderIter( + c *CommitNode, + seenExternal map[string]bool, + ignore []hash.Hash, +) CommitIter { + seen := make(map[string]bool) + for _, h := range ignore { + seen[h.Hex()] = true + } + + return &commitPreIterator{ + seenExternal: seenExternal, + seen: seen, + stack: make([]CommitIter, 0), + start: c, + } +} + +func (w *commitPreIterator) Next() (*CommitNode, error) { + var c *CommitNode + for { + if w.start != nil { + c = w.start + w.start = nil + } else { + current := len(w.stack) - 1 + if current < 0 { + return nil, io.EOF + } + + var err error + c, err = w.stack[current].Next() + if err == io.EOF { + w.stack = w.stack[:current] + continue + } + + if err != nil { + return nil, err + } + } + + if w.seen[c.Hash.Hex()] || w.seenExternal[c.Hash.Hex()] { + continue + } + + w.seen[c.Hash.Hex()] = true + + if c.NumParents() > 0 { + commitIter, err := filteredParentIter(c, w.seen) + if err != nil { + return nil, err + } + w.stack = append(w.stack, commitIter) + } + + return c, nil + } +} + +func filteredParentIter(c *CommitNode, seen map[string]bool) (CommitIter, error) { + var hashes []hash.Hash + for _, h := range c.ParentHashes { + if !seen[h.Hex()] { + hashes = append(hashes, h) + } + } + commits, err := c.GetCommits(hashes) + if err != nil { + return nil, err + } + + return newArrayCommitIter(commits), nil +} + +func (w *commitPreIterator) ForEach(cb func(*CommitNode) error) error { + for { + c, err := w.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + + err = cb(c) + if err == storer.ErrStop { + break + } + if err != nil { + return err + } + } + + return nil +} + +type commitPostIterator struct { + stack []*CommitNode + seen map[string]bool +} + +// NewCommitPostorderIter returns a CommitIter that walks the commit +// history like WalkCommitHistory but in post-order. This means that after +// walking a merge commit, the merged commit will be walked before the base +// it was merged on. This can be useful if you wish to see the history in +// chronological order. Ignore allows to skip some commits from being iterated. +func NewCommitPostorderIter(c *CommitNode, ignore []hash.Hash) CommitIter { + seen := make(map[string]bool) + for _, h := range ignore { + seen[h.Hex()] = true + } + + return &commitPostIterator{ + stack: []*CommitNode{c}, + seen: seen, + } +} + +func (w *commitPostIterator) Next() (*CommitNode, error) { + for { + if len(w.stack) == 0 { + return nil, io.EOF + } + + c := w.stack[len(w.stack)-1] + w.stack = w.stack[:len(w.stack)-1] + + if w.seen[c.Hash.Hex()] { + continue + } + + w.seen[c.Hash.Hex()] = true + + parentCommits, err := c.Parents() + if err != nil { + return nil, err + } + return c, newArrayCommitIter(parentCommits).ForEach(func(p *CommitNode) error { + w.stack = append(w.stack, p) + return nil + }) + } +} + +func (w *commitPostIterator) ForEach(cb func(*CommitNode) error) error { + for { + c, err := w.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + + err = cb(c) + if errors.Is(err, storer.ErrStop) { + break + } + if err != nil { + return err + } + } + + return nil +} diff --git a/versionmgr/commit_walker_bfs.go b/versionmgr/commit_walker_bfs.go new file mode 100644 index 00000000..58840a8c --- /dev/null +++ b/versionmgr/commit_walker_bfs.go @@ -0,0 +1,99 @@ +package versionmgr + +import ( + "errors" + "io" + + "github.com/go-git/go-git/v5/plumbing/storer" + "github.com/jiaozifs/jiaozifs/utils/hash" +) + +type bfsCommitIterator struct { + seenExternal map[string]bool + seen map[string]bool + queue []*CommitNode +} + +// NewCommitIterBSF returns a CommitIter that walks the commit history, +// starting at the given commit and visiting its parents in pre-order. +// The given callback will be called for each visited commit. Each commit will +// be visited only once. If the callback returns an error, walking will stop +// and will return the error. Other errors might be returned if the history +// cannot be traversed (e.g. missing objects). Ignore allows to skip some +// commits from being iterated. +func NewCommitIterBSF( + c *CommitNode, + seenExternal map[string]bool, + ignore []hash.Hash, +) CommitIter { + seen := make(map[string]bool) + for _, h := range ignore { + seen[h.Hex()] = true + } + + return &bfsCommitIterator{ + seenExternal: seenExternal, + seen: seen, + queue: []*CommitNode{c}, + } +} + +func (w *bfsCommitIterator) appendHash(store *CommitNode, h hash.Hash) error { + if w.seen[h.Hex()] || w.seenExternal[h.Hex()] { + return nil + } + c, err := store.GetCommit(h) + if err != nil { + return err + } + w.queue = append(w.queue, c) + return nil +} + +func (w *bfsCommitIterator) Next() (*CommitNode, error) { + var c *CommitNode + for { + if len(w.queue) == 0 { + return nil, io.EOF + } + c = w.queue[0] + w.queue = w.queue[1:] + + if w.seen[c.Hash.Hex()] || w.seenExternal[c.Hash.Hex()] { + continue + } + + w.seen[c.Hash.Hex()] = true + + for _, h := range c.ParentHashes { + err := w.appendHash(c, h) + if err != nil { + return nil, err + } + } + + return c, nil + } +} + +func (w *bfsCommitIterator) ForEach(cb func(node *CommitNode) error) error { + for { + c, err := w.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + + err = cb(c) + if errors.Is(err, storer.ErrStop) { + break + } + if err != nil { + return err + } + } + + return nil +} diff --git a/versionmgr/commit_walker_bfs_filtered.go b/versionmgr/commit_walker_bfs_filtered.go new file mode 100644 index 00000000..9729c89f --- /dev/null +++ b/versionmgr/commit_walker_bfs_filtered.go @@ -0,0 +1,179 @@ +package versionmgr + +import ( + "errors" + "io" + + "github.com/go-git/go-git/v5/plumbing/storer" + "github.com/jiaozifs/jiaozifs/utils/hash" +) + +// NewFilterCommitIter returns a CommitIter that walks the commit history, +// starting at the passed commit and visiting its parents in Breadth-first order. +// The commits returned by the CommitIter will validate the passed CommitFilter. +// The history won't be transversed beyond a commit if isLimit is true for it. +// Each commit will be visited only once. +// If the commit history can not be traversed, or the Close() method is called, +// the CommitIter won't return more commits. +// If no isValid is passed, all ancestors of from commit will be valid. +// If no isLimit is limit, all ancestors of all commits will be visited. +func NewFilterCommitIter( + from *CommitNode, + isValid *CommitFilter, + isLimit *CommitFilter, +) CommitIter { + var validFilter CommitFilter + if isValid == nil { + validFilter = func(_ *CommitNode) bool { + return true + } + } else { + validFilter = *isValid + } + + var limitFilter CommitFilter + if isLimit == nil { + limitFilter = func(_ *CommitNode) bool { + return false + } + } else { + limitFilter = *isLimit + } + + return &filterCommitIter{ + isValid: validFilter, + isLimit: limitFilter, + visited: map[string]struct{}{}, + queue: []*CommitNode{from}, + } +} + +// CommitFilter returns a boolean for the passed Commit +type CommitFilter func(*CommitNode) bool + +// filterCommitIter implements CommitIter +type filterCommitIter struct { + isValid CommitFilter + isLimit CommitFilter + visited map[string]struct{} + queue []*CommitNode + lastErr error +} + +// Next returns the next commit of the CommitIter. +// It will return io.EOF if there are no more commits to visit, +// or an error if the history could not be traversed. +func (w *filterCommitIter) Next() (*CommitNode, error) { + var commit *CommitNode + var err error + for { + commit, err = w.popNewFromQueue() + if err != nil { + return nil, w.close(err) + } + + w.visited[commit.Hash.Hex()] = struct{}{} + + if !w.isLimit(commit) { + err = w.addToQueue(commit, commit.ParentHashes...) + if err != nil { + return nil, w.close(err) + } + } + + if w.isValid(commit) { + return commit, nil + } + } +} + +// ForEach runs the passed callback over each Commit returned by the CommitIter +// until the callback returns an error or there is no more commits to traverse. +func (w *filterCommitIter) ForEach(cb func(*CommitNode) error) error { + for { + commit, err := w.Next() + if err == io.EOF { + break + } + + if err != nil { + return err + } + + err = cb(commit) + if errors.Is(err, storer.ErrStop) { + break + } + + if err != nil { + return err + } + } + + return nil +} + +// Error returns the error that caused that the CommitIter is no longer returning commits +func (w *filterCommitIter) Error() error { + return w.lastErr +} + +// Close closes the CommitIter +func (w *filterCommitIter) Close() { + w.visited = map[string]struct{}{} + w.queue = []*CommitNode{} + w.isLimit = nil + w.isValid = nil +} + +// close closes the CommitIter with an error +func (w *filterCommitIter) close(err error) error { + w.Close() + w.lastErr = err + return err +} + +// popNewFromQueue returns the first new commit from the internal fifo queue, +// or an io.EOF error if the queue is empty +func (w *filterCommitIter) popNewFromQueue() (*CommitNode, error) { + var first *CommitNode + for { + if len(w.queue) == 0 { + if w.lastErr != nil { + return nil, w.lastErr + } + + return nil, io.EOF + } + + first = w.queue[0] + w.queue = w.queue[1:] + if _, ok := w.visited[first.Hash.Hex()]; ok { + continue + } + + return first, nil + } +} + +// addToQueue adds the passed commits to the internal fifo queue if they weren't seen +// or returns an error if the passed hashes could not be used to get valid commits +func (w *filterCommitIter) addToQueue( + c *CommitNode, + hashes ...hash.Hash, +) error { + for _, hash := range hashes { + if _, ok := w.visited[hash.Hex()]; ok { + continue + } + + commit, err := c.GetCommit(hash) + if err != nil { + return err + } + + w.queue = append(w.queue, commit) + } + + return nil +} diff --git a/versionmgr/merge_base.go b/versionmgr/merge_base.go new file mode 100644 index 00000000..801f9f95 --- /dev/null +++ b/versionmgr/merge_base.go @@ -0,0 +1,211 @@ +package versionmgr + +import ( + "bytes" + "errors" + "fmt" + "sort" + + "github.com/go-git/go-git/v5/plumbing/storer" +) + +// errIsReachable is thrown when first commit is an ancestor of the second +var errIsReachable = fmt.Errorf("first is reachable from second") + +// MergeBase mimics the behavior of `git merge-base actual other`, returning the +// best common ancestor between the actual and the passed one. +// The best common ancestors can not be reached from other common ancestors. +func (c *CommitNode) MergeBase(other *CommitNode) ([]*CommitNode, error) { + // use sortedByCommitDateDesc strategy + sorted := sortByCommitDateDesc(c, other) + newer := sorted[0] + older := sorted[1] + + newerHistory, err := ancestorsIndex(older, newer) + if errors.Is(err, errIsReachable) { + return []*CommitNode{older}, nil + } + + if err != nil { + return nil, err + } + + var res []*CommitNode + inNewerHistory := isInIndexCommitFilter(newerHistory) + resIter := NewFilterCommitIter(older, &inNewerHistory, &inNewerHistory) + _ = resIter.ForEach(func(commit *CommitNode) error { + res = append(res, commit) + return nil + }) + + return Independents(res) +} + +// IsAncestor returns true if the actual commit is ancestor of the passed one. +// It returns an error if the history is not transversable +// It mimics the behavior of `git merge --is-ancestor actual other` +func (c *CommitNode) IsAncestor(other *CommitNode) (bool, error) { + found := false + iter := NewCommitPreorderIter(other, nil, nil) + err := iter.ForEach(func(comm *CommitNode) error { + if !bytes.Equal(comm.Hash, c.Hash) { + return nil + } + + found = true + return ErrStop + }) + + return found, err +} + +// ancestorsIndex returns a map with the ancestors of the starting commit if the +// excluded one is not one of them. It returns errIsReachable if the excluded commit +// is ancestor of the starting, or another error if the history is not traversable. +func ancestorsIndex(excluded, starting *CommitNode) (map[string]struct{}, error) { + if bytes.Equal(excluded.Hash, starting.Hash) { + return nil, errIsReachable + } + + startingHistory := map[string]struct{}{} + startingIter := NewCommitIterBSF(starting, nil, nil) + err := startingIter.ForEach(func(commit *CommitNode) error { + if bytes.Equal(commit.Hash, excluded.Hash) { + return errIsReachable + } + + startingHistory[commit.Hash.Hex()] = struct{}{} + return nil + }) + + if err != nil { + return nil, err + } + + return startingHistory, nil +} + +// Independents returns a subset of the passed commits, that are not reachable the others +// It mimics the behavior of `git merge-base --independent commit...`. +func Independents(commits []*CommitNode) ([]*CommitNode, error) { + // use sortedByCommitDateDesc strategy + candidates := sortByCommitDateDesc(commits...) + candidates = removeDuplicated(candidates) + + seen := map[string]struct{}{} + var isLimit CommitFilter = func(commit *CommitNode) bool { + _, ok := seen[commit.Hash.Hex()] + return ok + } + + if len(candidates) < 2 { + return candidates, nil + } + + pos := 0 + for { + from := candidates[pos] + others := remove(candidates, from) + fromHistoryIter := NewFilterCommitIter(from, nil, &isLimit) + err := fromHistoryIter.ForEach(func(fromAncestor *CommitNode) error { + for _, other := range others { + if bytes.Equal(fromAncestor.Hash, other.Hash) { + candidates = remove(candidates, other) + others = remove(others, other) + } + } + + if len(candidates) == 1 { + return storer.ErrStop + } + + seen[fromAncestor.Hash.Hex()] = struct{}{} + return nil + }) + + if err != nil { + return nil, err + } + + nextPos := indexOf(candidates, from) + 1 + if nextPos >= len(candidates) { + break + } + + pos = nextPos + } + + return candidates, nil +} + +// sortByCommitDateDesc returns the passed commits, sorted by `committer.When desc` +// +// Following this strategy, it is tried to reduce the time needed when walking +// the history from one commit to reach the others. It is assumed that ancestors +// use to be committed before its descendant; +// That way `Independents(A^, A)` will be processed as being `Independents(A, A^)`; +// so starting by `A` it will be reached `A^` way sooner than walking from `A^` +// to the initial commit, and then from `A` to `A^`. +func sortByCommitDateDesc(commits ...*CommitNode) []*CommitNode { + sorted := make([]*CommitNode, len(commits)) + copy(sorted, commits) + sort.Slice(sorted, func(i, j int) bool { + return sorted[i].Committer.When.After(sorted[j].Committer.When) + }) + + return sorted +} + +// indexOf returns the first position where target was found in the passed commits +func indexOf(commits []*CommitNode, target *CommitNode) int { + for i, commit := range commits { + if bytes.Equal(target.Hash, commit.Hash) { + return i + } + } + + return -1 +} + +// remove returns the passed commits excluding the commit toDelete +func remove(commits []*CommitNode, toDelete *CommitNode) []*CommitNode { + res := make([]*CommitNode, len(commits)) + j := 0 + for _, commit := range commits { + if bytes.Equal(commit.Hash, toDelete.Hash) { + continue + } + + res[j] = commit + j++ + } + + return res[:j] +} + +// removeDuplicated removes duplicated commits from the passed slice of commits +func removeDuplicated(commits []*CommitNode) []*CommitNode { + seen := make(map[string]struct{}, len(commits)) + res := make([]*CommitNode, len(commits)) + j := 0 + for _, commit := range commits { + if _, ok := seen[commit.Hash.Hex()]; ok { + continue + } + + seen[commit.Hash.Hex()] = struct{}{} + res[j] = commit + j++ + } + + return res[:j] +} + +// isInIndexCommitFilter returns a commitFilter that returns true +// if the commit is in the passed index. +func isInIndexCommitFilter(index map[string]struct{}) CommitFilter { + return func(c *CommitNode) bool { + _, ok := index[c.Hash.Hex()] + return ok + } +} diff --git a/versionmgr/merge_base_test.go b/versionmgr/merge_base_test.go new file mode 100644 index 00000000..f267efbd --- /dev/null +++ b/versionmgr/merge_base_test.go @@ -0,0 +1,107 @@ +package versionmgr + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/jiaozifs/jiaozifs/utils/hash" + + "github.com/stretchr/testify/require" + + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/testhelper" +) + +func TestCommitNode_MergeBase(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + objRepo := models.NewObjectRepo(db) + //mock data + // | -> c ------- + // | | + //a ------> b ------d----f---------merge? + // | | + // | ----------------->e---- + + testData := ` +a| +b|a +c|a +d|b,c +f|d +e|b +` + commitMap, err := loadCommitTestData(ctx, objRepo, testData) + require.NoError(t, err) + + t.Run("simple", func(t *testing.T) { + //simple + baseCommit := commitMap["b"] + mergeCommit := commitMap["c"] + ancestorNode, err := baseCommit.MergeBase(mergeCommit) + require.NoError(t, err) + require.Len(t, ancestorNode, 1) + require.Equal(t, "a", string(ancestorNode[0].Hash)) + }) + + t.Run("multiple merge", func(t *testing.T) { + baseCommit := commitMap["f"] + mergeCommit := commitMap["e"] + ancestorNode, err := baseCommit.MergeBase(mergeCommit) + require.NoError(t, err) + require.Len(t, ancestorNode, 1) + require.Equal(t, "b", string(ancestorNode[0].Hash)) + }) +} + +func loadCommitTestData(ctx context.Context, objRepo models.IObjectRepo, testData string) (map[string]*CommitNode, error) { + lines := strings.Split(testData, "\n") + commitMap := make(map[string]*CommitNode) + for _, line := range lines { + if len(strings.TrimSpace(line)) == 0 { + continue + } + commitData := strings.Split(strings.TrimSpace(line), "|") + hashName := strings.TrimSpace(commitData[0]) + commit := newCommit(hashName, strings.Split(commitData[1], ",")) + commitMap[hashName] = &CommitNode{ + Ctx: ctx, + Commit: *commit, + Object: objRepo, + } + _, err := objRepo.Insert(ctx, commit.Object()) + if err != nil { + return nil, err + } + } + return commitMap, nil +} + +func newCommit(hashStr string, parentHash []string) *models.Commit { + var p []hash.Hash + for _, pHashStr := range parentHash { + pHashStr = strings.TrimSpace(pHashStr) + if len(pHashStr) == 0 { + continue + } + p = append(p, hash.Hash(pHashStr)) + } + return &models.Commit{ + Hash: hash.Hash(hashStr), + Type: models.CommitObject, + Author: models.Signature{}, + Committer: models.Signature{ + When: time.Now(), + }, + MergeTag: "", + Message: hashStr, + TreeHash: nil, + ParentHashes: p, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + } +} diff --git a/versionmgr/tree_node.go b/versionmgr/tree_node.go new file mode 100644 index 00000000..764176ce --- /dev/null +++ b/versionmgr/tree_node.go @@ -0,0 +1,61 @@ +package versionmgr + +import ( + "context" + + "github.com/go-git/go-git/v5/utils/merkletrie/noder" + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/models/filemode" +) + +var _ noder.Noder = (*TreeNode)(nil) + +type TreeNode struct { + Ctx context.Context + models.TreeEntry + Object models.IObjectRepo +} + +func (n TreeNode) Hash() []byte { + return n.TreeEntry.Hash +} + +func (n TreeNode) String() string { + return n.TreeEntry.Name + " " + n.TreeEntry.Hash.Hex() +} + +func (n TreeNode) Name() string { + return n.TreeEntry.Name +} + +func (n TreeNode) IsDir() bool { + return n.TreeEntry.Mode == filemode.Dir +} + +func (n TreeNode) Children() ([]noder.Noder, error) { + treeNode, err := n.Object.TreeNode(n.Ctx, n.Hash()) + if err != nil { + return nil, err + } + children := make([]noder.Noder, len(treeNode.SubObjects)) + for i, sub := range treeNode.SubObjects { + children[i] = TreeNode{ + Ctx: n.Ctx, + TreeEntry: sub, + Object: n.Object, + } + } + return children, nil +} + +func (n TreeNode) NumChildren() (int, error) { + treeNode, err := n.Object.TreeNode(n.Ctx, n.Hash()) + if err != nil { + return 0, err + } + return len(treeNode.SubObjects), nil +} + +func (n TreeNode) Skip() bool { + return false +} From b83fbbb4b7a6f7250d3232d3f8fb59ce4e1aafe8 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Sun, 10 Dec 2023 10:59:56 +0800 Subject: [PATCH 054/210] refrator: make tree node easy to use --- block/gs/adapter.go | 2 +- controller/object_ctl.go | 31 +- go.mod | 5 - go.sum | 9 - models/object.go | 16 + versionmgr/commit.go | 31 +- versionmgr/commit_node.go | 14 +- versionmgr/commit_test.go | 13 +- versionmgr/tree_node.go | 91 ++++-- versionmgr/{tree.go => worktree.go} | 278 +++++++++--------- versionmgr/{tree_test.go => worktree_test.go} | 51 ++-- 11 files changed, 304 insertions(+), 237 deletions(-) rename versionmgr/{tree.go => worktree.go} (51%) rename versionmgr/{tree_test.go => worktree_test.go} (70%) diff --git a/block/gs/adapter.go b/block/gs/adapter.go index d19ebecc..bb6f2dde 100644 --- a/block/gs/adapter.go +++ b/block/gs/adapter.go @@ -220,7 +220,7 @@ func (a *Adapter) Remove(ctx context.Context, obj block.ObjectPointer) error { } err = a.client.Bucket(bucket).Object(key).Delete(ctx) if err != nil { - return fmt.Errorf("Object(%q).Delete: %w", key, err) + return fmt.Errorf("object(%q).Delete: %w", key, err) } return nil } diff --git a/controller/object_ctl.go b/controller/object_ctl.go index c1b4d54a..38e20eee 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -75,13 +75,13 @@ func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsRespo } objRepo := oct.Repo.ObjectRepo() - treeOp := versionmgr.NewTreeOp(oct.Repo.ObjectRepo()) - treeNode, err := objRepo.TreeNode(ctx, commit.TreeHash) + treeOp, err := versionmgr.NewWorkTree(ctx, oct.Repo.ObjectRepo(), models.NewRootTreeEntry(commit.TreeHash)) if err != nil { w.Error(err) return } - existNodes, missingPath, err := treeOp.MatchPath(ctx, treeNode, params.Path) + + existNodes, missingPath, err := treeOp.MatchPath(ctx, params.Path) if err != nil { w.Error(err) return @@ -93,19 +93,19 @@ func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsRespo objectWithName := existNodes[len(existNodes)-1] - blob, err := objRepo.Blob(ctx, objectWithName.Node.Hash) + blob, err := objRepo.Blob(ctx, objectWithName.Node().Hash) if err != nil { w.Error(err) return } //lookup files - etag := httputil.ETag(objectWithName.Node.Hash.Hex()) + etag := httputil.ETag(objectWithName.Node().Hash.Hex()) w.Header().Set("ETag", etag) - lastModified := httputil.HeaderTimestamp(objectWithName.Node.CreatedAt) + lastModified := httputil.HeaderTimestamp(objectWithName.Node().CreatedAt) w.Header().Set("Last-Modified", lastModified) w.Header().Set("Accept-Ranges", "bytes") - w.Header().Set("Content-Type", httputil.ExtensionsByType(objectWithName.Name)) + w.Header().Set("Content-Type", httputil.ExtensionsByType(objectWithName.Entry().Name)) // for security, make sure the browser and any proxies en route don't cache the response w.Header().Set("Cache-Control", "no-store, must-revalidate") w.Header().Set("Expires", "0") @@ -173,12 +173,6 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes var response api.ObjectStats err = oct.Repo.Transaction(ctx, func(dRepo models.IRepo) error { - treeOp := versionmgr.TreeOp{Object: dRepo.ObjectRepo()} - blob, err := treeOp.WriteBlob(ctx, oct.BlockAdapter, reader, r.ContentLength, block.PutOpts{}) - if err != nil { - return err - } - user, err := dRepo.UserRepo().Get(ctx, &models.GetUserParam{Name: utils.String(userName)}) if err != nil { return err @@ -200,12 +194,17 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes return err } - workingTreeID, err := dRepo.ObjectRepo().TreeNode(ctx, stash.CurrentTree) + workingTree, err := versionmgr.NewWorkTree(ctx, dRepo.ObjectRepo(), models.NewRootTreeEntry(stash.CurrentTree)) + if err != nil { + return err + } + + blob, err := workingTree.WriteBlob(ctx, oct.BlockAdapter, reader, r.ContentLength, block.PutOpts{}) if err != nil { return err } - newRoot, err := treeOp.AddLeaf(ctx, workingTreeID, params.Path, blob) + err = workingTree.AddLeaf(ctx, params.Path, blob) if err != nil { return err } @@ -218,7 +217,7 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes ContentType: &contentType, Metadata: &api.ObjectUserMetadata{}, } - return dRepo.WipRepo().UpdateCurrentHash(ctx, stash.ID, newRoot.Hash) + return dRepo.WipRepo().UpdateCurrentHash(ctx, stash.ID, workingTree.Root().Hash()) }) if err != nil { diff --git a/go.mod b/go.mod index 6275ebde..5d37f56a 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,6 @@ require ( github.com/getkin/kin-openapi v0.118.0 github.com/go-chi/chi/v5 v5.0.10 github.com/go-chi/cors v1.2.1 - github.com/go-git/go-git v4.7.0+incompatible github.com/go-git/go-git/v5 v5.10.1 github.com/go-openapi/swag v0.22.4 github.com/go-test/deep v1.1.0 @@ -87,7 +86,6 @@ require ( github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/containerd/continuity v0.3.0 // indirect - github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/docker/cli v23.0.6+incompatible // indirect github.com/docker/docker v23.0.6+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect @@ -95,8 +93,6 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/fatih/color v1.15.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -179,7 +175,6 @@ require ( google.golang.org/grpc v1.58.3 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect mellium.im/sasl v0.3.1 // indirect ) diff --git a/go.sum b/go.sum index 556980f7..fe84b7a0 100644 --- a/go.sum +++ b/go.sum @@ -133,7 +133,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= -github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -170,12 +169,7 @@ github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= -github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= -github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= -github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= -github.com/go-git/go-git v4.7.0+incompatible h1:+W9rgGY4DOKKdX2x6HxSR7HNeTxqiKrOvKnuittYVdA= -github.com/go-git/go-git v4.7.0+incompatible/go.mod h1:6+421e08gnZWn30y26Vchf7efgYLe4dl5OQbBSUXShE= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git/v5 v5.10.1 h1:tu8/D8i+TWxgKpzQ3Vc43e+kkhXqtsZCKI/egajKnxk= github.com/go-git/go-git/v5 v5.10.1/go.mod h1:uEuHjxkHap8kAl//V5F/nNWwqIYtP/402ddd05mp0wg= @@ -368,7 +362,6 @@ github.com/oapi-codegen/nethttp-middleware v1.0.1 h1:ZWvwfnMU0eloHX1VEJmQscQm374 github.com/oapi-codegen/nethttp-middleware v1.0.1/go.mod h1:P7xtAvpoqNB+5obR9qRCeefH7YlXWSK3KgPs/9WB8tE= github.com/oapi-codegen/runtime v1.1.0 h1:rJpoNUawn5XTvekgfkvSZr0RqEnoYpFkyvrzfWeFKWM= github.com/oapi-codegen/runtime v1.1.0/go.mod h1:BeSfBkWWWnAnGdyS+S/GnlbmHKzf8/hwkvelJZDeKA8= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= @@ -841,8 +834,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/models/object.go b/models/object.go index 88747b75..0ad94c56 100644 --- a/models/object.go +++ b/models/object.go @@ -1,6 +1,7 @@ package models import ( + "bytes" "context" "time" @@ -38,6 +39,17 @@ type TreeEntry struct { Hash hash.Hash `bun:"hash"` } +func NewRootTreeEntry(hash hash.Hash) TreeEntry { + return TreeEntry{ + Name: "", + Mode: filemode.Dir, + Hash: hash, + } +} +func (treeEntry TreeEntry) Equal(other TreeEntry) bool { + return bytes.Equal(treeEntry.Hash, other.Hash) && treeEntry.Mode == other.Mode && treeEntry.Name == other.Name +} + type Blob struct { bun.BaseModel `bun:"table:object"` Hash hash.Hash `bun:"hash,pk,type:bytea"` @@ -188,6 +200,10 @@ func (commit *Commit) GetHash() (hash.Hash, error) { } _, err = hasher.Write(commit.TreeHash) + if err != nil { + return nil, err + } + for _, h := range commit.ParentHashes { _, err = hasher.Write(h) if err != nil { diff --git a/versionmgr/commit.go b/versionmgr/commit.go index 544c1088..385e4fb8 100644 --- a/versionmgr/commit.go +++ b/versionmgr/commit.go @@ -116,25 +116,24 @@ func (commitOp *CommitOp) DiffCommit(ctx context.Context, baseCommitID, toCommit return nil, err } - fromNode := &TreeNode{ - Ctx: ctx, - TreeEntry: models.TreeEntry{ - Name: "", - Mode: filemode.Dir, - Hash: baseCommit.TreeHash, - }, - Object: commitOp.Object, + fromNode, err := NewTreeNode(ctx, models.TreeEntry{ + Name: "", + Mode: filemode.Dir, + Hash: baseCommit.TreeHash, + }, commitOp.Object) + if err != nil { + return nil, err } - toNode := &TreeNode{ - Ctx: ctx, - TreeEntry: models.TreeEntry{ - Name: "", - Mode: filemode.Dir, - Hash: toCommit.TreeHash, - }, - Object: commitOp.Object, + toNode, err := NewTreeNode(ctx, models.TreeEntry{ + Name: "", + Mode: filemode.Dir, + Hash: toCommit.TreeHash, + }, commitOp.Object) + if err != nil { + return nil, err } + return merkletrie.DiffTreeContext(ctx, fromNode, toNode, func(a, b noder.Hasher) bool { return bytes.Equal(a.Hash(), b.Hash()) }) diff --git a/versionmgr/commit_node.go b/versionmgr/commit_node.go index 9b796a43..3360010e 100644 --- a/versionmgr/commit_node.go +++ b/versionmgr/commit_node.go @@ -29,15 +29,11 @@ func (c *CommitNode) Tree() (*TreeNode, error) { if err != nil { return nil, err } - return &TreeNode{ - Ctx: c.Ctx, - TreeEntry: models.TreeEntry{ - Name: "", - Mode: filemode.Dir, - Hash: treeNode.Hash, - }, - Object: c.Object, - }, nil + return NewTreeNode(c.Ctx, models.TreeEntry{ + Name: "", + Mode: filemode.Dir, + Hash: treeNode.Hash, + }, c.Object) } // Parents return a CommitIter to the parent Commits. diff --git a/versionmgr/commit_test.go b/versionmgr/commit_test.go index 6e12ae7a..0feacc5d 100644 --- a/versionmgr/commit_test.go +++ b/versionmgr/commit_test.go @@ -68,7 +68,7 @@ b/e.txt |e2 func makeUser(ctx context.Context, userRepo models.IUserRepo, name string) (*models.User, error) { user := &models.User{ - Name: "name", + Name: name, Email: "xxx@gg.com", EncryptedPassword: "123", CurrentSignInAt: time.Time{}, @@ -120,9 +120,10 @@ func makeWip(ctx context.Context, wipRepo models.IWipRepo, repoID, refID uuid.UU } func makeRoot(ctx context.Context, objRepo models.IObjectRepo, testData string) (*models.TreeNode, error) { lines := strings.Split(testData, "\n") - treeOp := NewTreeOp(objRepo) - root := EmptyRoot - var err error + treeOp, err := NewWorkTree(ctx, objRepo, EmptyDirEntry) + if err != nil { + return nil, err + } for _, line := range lines { if len(strings.TrimSpace(line)) == 0 { continue @@ -138,10 +139,10 @@ func makeRoot(ctx context.Context, objRepo models.IObjectRepo, testData string) UpdatedAt: time.Now(), } - root, err = treeOp.AddLeaf(ctx, root, fullPath, blob) + err = treeOp.AddLeaf(ctx, fullPath, blob) if err != nil { return nil, err } } - return root, nil + return treeOp.Root().TreeNode(), nil } diff --git a/versionmgr/tree_node.go b/versionmgr/tree_node.go index 764176ce..d2c786cb 100644 --- a/versionmgr/tree_node.go +++ b/versionmgr/tree_node.go @@ -2,6 +2,7 @@ package versionmgr import ( "context" + "fmt" "github.com/go-git/go-git/v5/utils/merkletrie/noder" "github.com/jiaozifs/jiaozifs/models" @@ -11,45 +12,99 @@ import ( var _ noder.Noder = (*TreeNode)(nil) type TreeNode struct { - Ctx context.Context - models.TreeEntry - Object models.IObjectRepo + ctx context.Context + entry models.TreeEntry + treeNode *models.TreeNode + objectRepo models.IObjectRepo +} + +func NewTreeNode(ctx context.Context, entry models.TreeEntry, object models.IObjectRepo) (*TreeNode, error) { + treeNode := EmptyRoot + if !entry.Equal(EmptyDirEntry) { + var err error + treeNode, err = object.TreeNode(ctx, entry.Hash) + if err != nil { + return nil, err + } + } + + return &TreeNode{ctx: ctx, entry: entry, treeNode: treeNode, objectRepo: object}, nil +} + +func (n TreeNode) Type() models.ObjectType { + return n.treeNode.Type +} +func (n TreeNode) SubObjects() []models.TreeEntry { + return n.treeNode.SubObjects +} + +func (n TreeNode) TreeNode() *models.TreeNode { + return n.treeNode +} + +func (n TreeNode) SubDir(ctx context.Context, name string) (*TreeNode, error) { + for _, node := range n.treeNode.SubObjects { + if node.Name == name { + if node.Mode == filemode.Dir { + return NewTreeNode(ctx, node, n.objectRepo) + } + return nil, fmt.Errorf("node is not directory") + } + } + return nil, ErrPathNotFound +} + +func (n TreeNode) SubFile(ctx context.Context, name string) (*models.Blob, error) { + for _, node := range n.treeNode.SubObjects { + if node.Name == name { + if node.Mode == filemode.Regular || node.Mode == filemode.Executable { + return n.objectRepo.Blob(ctx, node.Hash) + } + return nil, fmt.Errorf("node is not blob") + } + } + return nil, ErrPathNotFound +} + +func (n TreeNode) SubEntry(_ context.Context, name string) (models.TreeEntry, error) { + for _, node := range n.treeNode.SubObjects { + if node.Name == name { + return node, nil + } + } + return models.TreeEntry{}, ErrPathNotFound } func (n TreeNode) Hash() []byte { - return n.TreeEntry.Hash + return n.entry.Hash } func (n TreeNode) String() string { - return n.TreeEntry.Name + " " + n.TreeEntry.Hash.Hex() + return n.entry.Name + " " + n.entry.Hash.Hex() } func (n TreeNode) Name() string { - return n.TreeEntry.Name + return n.entry.Name } func (n TreeNode) IsDir() bool { - return n.TreeEntry.Mode == filemode.Dir + return n.entry.Mode == filemode.Dir } func (n TreeNode) Children() ([]noder.Noder, error) { - treeNode, err := n.Object.TreeNode(n.Ctx, n.Hash()) - if err != nil { - return nil, err - } - children := make([]noder.Noder, len(treeNode.SubObjects)) - for i, sub := range treeNode.SubObjects { - children[i] = TreeNode{ - Ctx: n.Ctx, - TreeEntry: sub, - Object: n.Object, + children := make([]noder.Noder, len(n.treeNode.SubObjects)) + for i, sub := range n.treeNode.SubObjects { + var err error + children[i], err = NewTreeNode(n.ctx, sub, n.objectRepo) + if err != nil { + return nil, err } } return children, nil } func (n TreeNode) NumChildren() (int, error) { - treeNode, err := n.Object.TreeNode(n.Ctx, n.Hash()) + treeNode, err := n.objectRepo.TreeNode(n.ctx, n.Hash()) if err != nil { return 0, err } diff --git a/versionmgr/tree.go b/versionmgr/worktree.go similarity index 51% rename from versionmgr/tree.go rename to versionmgr/worktree.go index 7b6b7232..3e043bef 100644 --- a/versionmgr/tree.go +++ b/versionmgr/worktree.go @@ -25,29 +25,52 @@ var EmptyRoot = &models.TreeNode{ Type: models.TreeObject, } +var EmptyDirEntry = models.TreeEntry{ + Name: "", + Hash: hash.Hash([]byte{}), + Mode: filemode.Dir, +} + var ( ErrPathNotFound = fmt.Errorf("path not found") ErrEntryExit = fmt.Errorf("entry exit") ErrBlobMustBeLeaf = fmt.Errorf("blob must be leaf") - ErrNotDiretory = fmt.Errorf("path must be a directory") + ErrNotDirectory = fmt.Errorf("path must be a directory") ) -type ObjectWithName struct { - Node *models.Object - Name string +type FullObject struct { + node *models.Object + entry models.TreeEntry +} + +func (objectName FullObject) Entry() models.TreeEntry { + return objectName.entry +} +func (objectName FullObject) Node() *models.Object { + return objectName.node } -type TreeOp struct { - Object models.IObjectRepo +type WorkTree struct { + object models.IObjectRepo + root *TreeNode } -func NewTreeOp(object models.IObjectRepo) *TreeOp { - return &TreeOp{ - Object: object, +func NewWorkTree(ctx context.Context, object models.IObjectRepo, root models.TreeEntry) (*WorkTree, error) { + rootNode, err := NewTreeNode(ctx, root, object) + if err != nil { + return nil, err } + return &WorkTree{ + object: object, + root: rootNode, + }, nil } -func (treeOp *TreeOp) WriteBlob(ctx context.Context, adapter block.Adapter, body io.Reader, contentLength int64, opts block.PutOpts) (*models.Blob, error) { +func (workTree *WorkTree) Root() *TreeNode { + return workTree.root +} + +func (workTree *WorkTree) WriteBlob(ctx context.Context, adapter block.Adapter, body io.Reader, contentLength int64, opts block.PutOpts) (*models.Blob, error) { // handle the upload itself hashReader := hash.NewHashingReader(body, hash.Md5) tempf, err := os.CreateTemp("", "*") @@ -87,49 +110,20 @@ func (treeOp *TreeOp) WriteBlob(ctx context.Context, adapter block.Adapter, body }, nil } -func (treeOp *TreeOp) SubDir(ctx context.Context, tn *models.TreeNode, name string) (*models.TreeNode, error) { - for _, node := range tn.SubObjects { - if node.Name == name { - if node.Mode == filemode.Dir { - return treeOp.Object.TreeNode(ctx, node.Hash) - } - return nil, fmt.Errorf("node is not directory") - } - } - return nil, ErrPathNotFound -} - -func (treeOp *TreeOp) SubFile(ctx context.Context, tn *models.TreeNode, name string) (*models.Blob, error) { - for _, node := range tn.SubObjects { - if node.Name == name { - if node.Mode == filemode.Regular || node.Mode == filemode.Executable { - return treeOp.Object.Blob(ctx, node.Hash) - } - return nil, fmt.Errorf("node is not blob") - } - } - return nil, ErrPathNotFound -} - -func (treeOp *TreeOp) SubEntry(_ context.Context, tn *models.TreeNode, name string) (models.TreeEntry, error) { - for _, node := range tn.SubObjects { - if node.Name == name { - return node, nil - } +func (workTree *WorkTree) AppendDirectEntry(ctx context.Context, treeEntry models.TreeEntry) (*models.TreeNode, error) { + chilren, err := workTree.root.Children() + if err != nil { + return nil, err } - return models.TreeEntry{}, ErrPathNotFound -} - -func (treeOp *TreeOp) AppendTreeEntry(ctx context.Context, tn *models.TreeNode, treeEntry models.TreeEntry) (*models.TreeNode, error) { - for _, node := range tn.SubObjects { - if node.Name == treeEntry.Name { + for _, node := range chilren { + if node.Name() == treeEntry.Name { return nil, ErrEntryExit } } newTree := &models.TreeNode{ - Type: tn.Type, - SubObjects: tn.SubObjects, + Type: workTree.root.Type(), + SubObjects: workTree.root.SubObjects(), CreatedAt: time.Now(), UpdatedAt: time.Now(), } @@ -140,20 +134,20 @@ func (treeOp *TreeOp) AppendTreeEntry(ctx context.Context, tn *models.TreeNode, } newTree.Hash = hash - obj, err := treeOp.Object.Insert(ctx, newTree.Object()) + obj, err := workTree.object.Insert(ctx, newTree.Object()) if err != nil { return nil, err } return obj.TreeNode(), nil } -func (treeOp *TreeOp) DeleteDirectObject(ctx context.Context, tn *models.TreeNode, name string) (*models.TreeNode, bool, error) { +func (workTree *WorkTree) DeleteDirectEntry(ctx context.Context, name string) (*models.TreeNode, bool, error) { newTree := &models.TreeNode{ - Type: tn.Type, + Type: workTree.root.Type(), CreatedAt: time.Now(), UpdatedAt: time.Now(), } - for _, sub := range tn.SubObjects { + for _, sub := range workTree.root.SubObjects() { if sub.Name != name { //filter tree entry by name newTree.SubObjects = append(newTree.SubObjects, sub) } @@ -170,17 +164,17 @@ func (treeOp *TreeOp) DeleteDirectObject(ctx context.Context, tn *models.TreeNod } newTree.Hash = hash - obj, err := treeOp.Object.Insert(ctx, newTree.Object()) + obj, err := workTree.object.Insert(ctx, newTree.Object()) if err != nil { return nil, false, err } return obj.TreeNode(), false, nil } -func (treeOp *TreeOp) ReplaceTreeEntry(ctx context.Context, tn *models.TreeNode, treeEntry models.TreeEntry) (*models.TreeNode, error) { +func (workTree *WorkTree) ReplaceSubTreeEntry(ctx context.Context, treeEntry models.TreeEntry) (*models.TreeNode, error) { index := -1 var sub models.TreeEntry - for index, sub = range tn.SubObjects { + for index, sub = range workTree.root.SubObjects() { if sub.Name == treeEntry.Name { break } @@ -190,12 +184,12 @@ func (treeOp *TreeOp) ReplaceTreeEntry(ctx context.Context, tn *models.TreeNode, } newTree := &models.TreeNode{ - Type: tn.Type, - SubObjects: make([]models.TreeEntry, len(tn.SubObjects)), + Type: workTree.root.Type(), + SubObjects: make([]models.TreeEntry, len(workTree.root.SubObjects())), CreatedAt: time.Now(), UpdatedAt: time.Now(), } - copy(newTree.SubObjects, tn.SubObjects) + copy(newTree.SubObjects, workTree.root.SubObjects()) newTree.SubObjects[index] = treeEntry hash, err := newTree.GetHash() @@ -204,45 +198,47 @@ func (treeOp *TreeOp) ReplaceTreeEntry(ctx context.Context, tn *models.TreeNode, } newTree.Hash = hash - obj, err := treeOp.Object.Insert(ctx, newTree.Object()) + obj, err := workTree.object.Insert(ctx, newTree.Object()) if err != nil { return nil, err } return obj.TreeNode(), nil } -func (treeOp *TreeOp) MatchPath(ctx context.Context, tn *models.TreeNode, path string) ([]ObjectWithName, []string, error) { +func (workTree *WorkTree) MatchPath(ctx context.Context, path string) ([]FullObject, []string, error) { pathSegs := strings.Split(filepath.Clean(path), fmt.Sprintf("%c", os.PathSeparator)) - var existNodes []ObjectWithName + var existNodes []FullObject var missingPath []string //a/b/c/d/e //a/b/c //a/b/c/d/e/f/g + + curNode := workTree.root for index, seg := range pathSegs { - entry, err := treeOp.SubEntry(ctx, tn, seg) + entry, err := curNode.SubEntry(ctx, seg) if errors.Is(err, ErrPathNotFound) { missingPath = pathSegs[index:] return existNodes, missingPath, nil } if entry.Mode == filemode.Dir { - tn, err = treeOp.SubDir(ctx, tn, entry.Name) + curNode, err = curNode.SubDir(ctx, entry.Name) if err != nil { return nil, nil, err } - existNodes = append(existNodes, ObjectWithName{ - Node: tn.Object(), - Name: entry.Name, + existNodes = append(existNodes, FullObject{ + node: curNode.TreeNode().Object(), + entry: entry, }) } else { //must be file - blob, err := treeOp.SubFile(ctx, tn, entry.Name) + blob, err := curNode.SubFile(ctx, entry.Name) if err != nil { return nil, nil, err } - existNodes = append(existNodes, ObjectWithName{ - Node: blob.Object(), - Name: entry.Name, + existNodes = append(existNodes, FullObject{ + node: blob.Object(), + entry: entry, }) if index != len(pathSegs)-1 { @@ -256,28 +252,28 @@ func (treeOp *TreeOp) MatchPath(ctx context.Context, tn *models.TreeNode, path s } // AddLeaf insert new leaf in entry, if path not exit, create new -func (treeOp *TreeOp) AddLeaf(ctx context.Context, root *models.TreeNode, fullPath string, blob *models.Blob) (*models.TreeNode, error) { - existNode, missingPath, err := treeOp.MatchPath(ctx, root, fullPath) +func (workTree *WorkTree) AddLeaf(ctx context.Context, fullPath string, blob *models.Blob) error { + existNode, missingPath, err := workTree.MatchPath(ctx, fullPath) if err != nil { - return nil, err + return err } if len(missingPath) == 0 { - return nil, ErrEntryExit + return ErrEntryExit } - _, err = treeOp.Object.Insert(ctx, blob.Object()) + _, err = workTree.object.Insert(ctx, blob.Object()) if err != nil { - return nil, err + return err } slices.Reverse(missingPath) var lastEntry models.TreeEntry for index, path := range missingPath { if index == 0 { - _, err = treeOp.Object.Insert(ctx, blob.Object()) + _, err = workTree.object.Insert(ctx, blob.Object()) if err != nil { - return nil, err + return err } lastEntry = models.TreeEntry{ Name: path, @@ -289,11 +285,11 @@ func (treeOp *TreeOp) AddLeaf(ctx context.Context, root *models.TreeNode, fullPa newTree, err := models.NewTreeNode(lastEntry) if err != nil { - return nil, err + return err } - _, err = treeOp.Object.Insert(ctx, newTree.Object()) + _, err = workTree.object.Insert(ctx, newTree.Object()) if err != nil { - return nil, err + return err } lastEntry = models.TreeEntry{ Name: path, @@ -303,50 +299,56 @@ func (treeOp *TreeOp) AddLeaf(ctx context.Context, root *models.TreeNode, fullPa } slices.Reverse(existNode) - existNode = append(existNode, ObjectWithName{ - Node: root.Object(), - Name: "", //root node have no name + existNode = append(existNode, FullObject{ + node: workTree.root.TreeNode().Object(), + entry: models.NewRootTreeEntry(workTree.root.Hash()), //root node have no name }) - var newNode *models.TreeNode for index, node := range existNode { + newWorkTree, err := NewWorkTree(ctx, workTree.object, node.Entry()) + if err != nil { + return err + } + var newNode *models.TreeNode if index == 0 { //insert new node - newNode, err = treeOp.AppendTreeEntry(ctx, node.Node.TreeNode(), lastEntry) + newNode, err = newWorkTree.AppendDirectEntry(ctx, lastEntry) } else { //replace node - newNode, err = treeOp.ReplaceTreeEntry(ctx, node.Node.TreeNode(), lastEntry) + + newNode, err = newWorkTree.ReplaceSubTreeEntry(ctx, lastEntry) } if err != nil { - return nil, err + return err } lastEntry = models.TreeEntry{ - Name: node.Name, - Mode: filemode.Dir, + Name: node.Entry().Name, // use old name but replace with new hase + Mode: node.Entry().Mode, Hash: newNode.Hash, } } - return newNode, nil + workTree.root, err = NewTreeNode(ctx, lastEntry, workTree.object) + return err } // ReplaceLeaf replace leaf with a new blob, all parent directory updated -func (treeOp *TreeOp) ReplaceLeaf(ctx context.Context, root *models.TreeNode, fullPath string, blob *models.Blob) (*models.TreeNode, error) { - existNode, missingPath, err := treeOp.MatchPath(ctx, root, fullPath) +func (workTree *WorkTree) ReplaceLeaf(ctx context.Context, fullPath string, blob *models.Blob) error { + existNode, missingPath, err := workTree.MatchPath(ctx, fullPath) if err != nil { - return nil, err + return err } if len(missingPath) > 0 { - return nil, ErrPathNotFound + return ErrPathNotFound } - _, err = treeOp.Object.Insert(ctx, blob.Object()) + _, err = workTree.object.Insert(ctx, blob.Object()) if err != nil { - return nil, err + return err } slices.Reverse(existNode) - existNode = append(existNode, ObjectWithName{ - Node: root.Object(), - Name: "", //root node have no name + existNode = append(existNode, FullObject{ + node: workTree.root.TreeNode().Object(), + entry: models.NewRootTreeEntry(workTree.root.Hash()), //root node have no name }) var lastEntry models.TreeEntry @@ -354,24 +356,29 @@ func (treeOp *TreeOp) ReplaceLeaf(ctx context.Context, root *models.TreeNode, fu for index, node := range existNode { if index == 0 { lastEntry = models.TreeEntry{ - Name: node.Name, - Mode: filemode.Regular, + Name: node.Entry().Name, + Mode: node.Entry().Mode, Hash: blob.Hash, } continue } - newNode, err = treeOp.ReplaceTreeEntry(ctx, node.Node.TreeNode(), lastEntry) + subWorkTree, err := NewWorkTree(ctx, workTree.object, node.Entry()) + if err != nil { + return err + } + newNode, err = subWorkTree.ReplaceSubTreeEntry(ctx, lastEntry) if err != nil { - return nil, err + return err } lastEntry = models.TreeEntry{ - Name: node.Name, - Mode: filemode.Dir, + Name: node.Entry().Name, + Mode: node.Entry().Mode, Hash: newNode.Hash, } } - return newNode, nil + workTree.root, err = NewTreeNode(ctx, lastEntry, workTree.object) + return err } // RemoveEntry remove tree entry from specific tree, if directory have only one entry, this directory was remove too @@ -385,61 +392,64 @@ func (treeOp *TreeOp) ReplaceLeaf(ctx context.Context, root *models.TreeNode, fu // RemoveEntry(ctx, root, "a") return empty root // RemoveEntry(ctx, root, "a/b/c.txt") return new root of(a/b/c.txt) // RemoveEntry(ctx, root, "a/b") return empty root. a b c.txt d.txt all removed -func (treeOp *TreeOp) RemoveEntry(ctx context.Context, root *models.TreeNode, fullPath string) (*models.TreeNode, error) { - existNode, missingPath, err := treeOp.MatchPath(ctx, root, fullPath) +func (workTree *WorkTree) RemoveEntry(ctx context.Context, fullPath string) error { + existNode, missingPath, err := workTree.MatchPath(ctx, fullPath) if err != nil { - return nil, err + return err } if len(missingPath) > 0 { - return nil, ErrPathNotFound + return ErrPathNotFound } slices.Reverse(existNode) - existNode = append(existNode, ObjectWithName{ - Node: root.Object(), - Name: "", //root node have no name + existNode = append(existNode, FullObject{ + node: workTree.root.TreeNode().Object(), + entry: models.NewRootTreeEntry(workTree.root.Hash()), //root node have no name }) - lastEntry := models.TreeEntry{ - Name: existNode[0].Name, - Mode: filemode.Dir, - Hash: existNode[0].Node.Hash, - } + lastEntry := existNode[0].Entry() existNode = existNode[1:] var newNode *models.TreeNode for index, node := range existNode { + subWorkTree, err := NewWorkTree(ctx, workTree.object, node.Entry()) + if err != nil { + return err + } if index == 0 || lastEntry.Hash.IsEmpty() { var isEmpty bool - newNode, isEmpty, err = treeOp.DeleteDirectObject(ctx, node.Node.TreeNode(), lastEntry.Name) + newNode, isEmpty, err = subWorkTree.DeleteDirectEntry(ctx, lastEntry.Name) if err != nil { - return nil, err + return err } lastEntry = models.TreeEntry{ - Name: node.Name, - Mode: filemode.Dir, + Name: node.Entry().Name, + Mode: node.Entry().Mode, } if !isEmpty { lastEntry.Hash = newNode.Hash } } else { - newNode, err = treeOp.ReplaceTreeEntry(ctx, node.Node.TreeNode(), lastEntry) + newNode, err = subWorkTree.ReplaceSubTreeEntry(ctx, lastEntry) if err != nil { - return nil, err + return err } lastEntry = models.TreeEntry{ - Name: node.Name, - Mode: filemode.Dir, + Name: node.Entry().Name, + Mode: node.Entry().Mode, Hash: newNode.Hash, } } } if newNode == nil { - return EmptyRoot, nil + workTree.root, _ = NewTreeNode(ctx, EmptyDirEntry, workTree.object) + return nil } - return newNode, nil + + workTree.root, err = NewTreeNode(ctx, lastEntry, workTree.object) + return err } // Ls list tree entry of specific path of specific root @@ -452,12 +462,12 @@ func (treeOp *TreeOp) RemoveEntry(ctx context.Context, root *models.TreeNode, fu // // Ls(ctx, root, "a") return b // Ls(ctx, root, "a/b" return c.txt and d.txt -func (treeOp *TreeOp) Ls(ctx context.Context, root *models.TreeNode, fullPath string) ([]models.TreeEntry, error) { +func (workTree *WorkTree) Ls(ctx context.Context, fullPath string) ([]models.TreeEntry, error) { if len(fullPath) == 0 { - return root.SubObjects, nil + return workTree.root.SubObjects(), nil } - existNode, missingPath, err := treeOp.MatchPath(ctx, root, fullPath) + existNode, missingPath, err := workTree.MatchPath(ctx, fullPath) if err != nil { return nil, err } @@ -467,9 +477,9 @@ func (treeOp *TreeOp) Ls(ctx context.Context, root *models.TreeNode, fullPath st } lastNode := existNode[len(existNode)-1] - if lastNode.Node.Type != models.TreeObject { - return nil, ErrNotDiretory + if lastNode.Node().Type != models.TreeObject { + return nil, ErrNotDirectory } - return lastNode.Node.SubObjects, nil + return lastNode.Node().SubObjects, nil } diff --git a/versionmgr/tree_test.go b/versionmgr/worktree_test.go similarity index 70% rename from versionmgr/tree_test.go rename to versionmgr/worktree_test.go index d5f7a585..a760437b 100644 --- a/versionmgr/tree_test.go +++ b/versionmgr/worktree_test.go @@ -6,6 +6,8 @@ import ( "errors" "testing" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -25,7 +27,8 @@ func TestTreeOpWriteBlob(t *testing.T) { adapter := mem.New(ctx) objRepo := models.NewObjectRepo(db) - treeOp := NewTreeOp(objRepo) + treeOp, err := NewWorkTree(ctx, objRepo, EmptyDirEntry) + require.NoError(t, err) binary := []byte("Build simple, secure, scalable systems with Go") bLen := int64(len(binary)) @@ -44,7 +47,8 @@ func TestTreeOpTreeOp(t *testing.T) { adapter := mem.New(ctx) objRepo := models.NewObjectRepo(db) - treeOp := NewTreeOp(objRepo) + treeOp, err := NewWorkTree(ctx, objRepo, EmptyDirEntry) + require.NoError(t, err) binary := []byte("Build simple, secure, scalable systems with Go") bLen := int64(len(binary)) @@ -52,12 +56,12 @@ func TestTreeOpTreeOp(t *testing.T) { blob, err := treeOp.WriteBlob(ctx, adapter, r, bLen, block.PutOpts{}) require.NoError(t, err) - oriRoot, err := treeOp.AddLeaf(ctx, EmptyRoot, "a/b/c.txt", blob) + err = treeOp.AddLeaf(ctx, "a/b/c.txt", blob) require.NoError(t, err) - require.Equal(t, "3bf643c30934d121ee45d413b165f135", oriRoot.Hash.Hex()) + require.Equal(t, "3bf643c30934d121ee45d413b165f135", hash.Hash(treeOp.Root().Hash()).Hex()) //add again expect get an error - _, err = treeOp.AddLeaf(ctx, oriRoot, "a/b/c.txt", blob) + err = treeOp.AddLeaf(ctx, "a/b/c.txt", blob) require.True(t, errors.Is(err, ErrEntryExit)) //update path @@ -67,21 +71,21 @@ func TestTreeOpTreeOp(t *testing.T) { blob, err = treeOp.WriteBlob(ctx, adapter, r, bLen, block.PutOpts{}) require.NoError(t, err) - updatedRoot, err := treeOp.ReplaceLeaf(ctx, oriRoot, "a/b/c.txt", blob) + err = treeOp.ReplaceLeaf(ctx, "a/b/c.txt", blob) require.NoError(t, err) - require.Equal(t, "8856b15f0f6c7ad21bfabe812df69e83", updatedRoot.Hash.Hex()) + require.Equal(t, "8856b15f0f6c7ad21bfabe812df69e83", hash.Hash(treeOp.Root().Hash()).Hex()) { //add another branch - updatedRoot, err = treeOp.AddLeaf(ctx, updatedRoot, "a/b/d.txt", blob) + err = treeOp.AddLeaf(ctx, "a/b/d.txt", blob) require.NoError(t, err) - require.Equal(t, "225f0ca6233681a441969922a7425db2", updatedRoot.Hash.Hex()) + require.Equal(t, "225f0ca6233681a441969922a7425db2", hash.Hash(treeOp.Root().Hash()).Hex()) } { //check fs structure - rootDir, err := objRepo.TreeNode(ctx, updatedRoot.Hash) + rootDir, err := objRepo.TreeNode(ctx, treeOp.Root().Hash()) require.NoError(t, err) require.Len(t, rootDir.SubObjects, 1) require.Equal(t, "a", rootDir.SubObjects[0].Name) @@ -100,25 +104,25 @@ func TestTreeOpTreeOp(t *testing.T) { { //check ls - subObjects, err := treeOp.Ls(ctx, updatedRoot, "a") + subObjects, err := treeOp.Ls(ctx, "a") require.NoError(t, err) require.Len(t, subObjects, 1) require.Equal(t, "b", subObjects[0].Name) - subObjects, err = treeOp.Ls(ctx, updatedRoot, "a/b") + subObjects, err = treeOp.Ls(ctx, "a/b") require.NoError(t, err) require.Len(t, subObjects, 2) require.Equal(t, "c.txt", subObjects[0].Name) require.Equal(t, "d.txt", subObjects[1].Name) } - rootAfterRemove, err := treeOp.RemoveEntry(ctx, updatedRoot, "a/b/c.txt") + err = treeOp.RemoveEntry(ctx, "a/b/c.txt") require.NoError(t, err) - require.Equal(t, "f90e2d306ad172824fa171b9e0d9e133", rootAfterRemove.Hash.Hex()) + require.Equal(t, "f90e2d306ad172824fa171b9e0d9e133", hash.Hash(treeOp.Root().Hash()).Hex()) - rootAfterRemoveAll, err := treeOp.RemoveEntry(ctx, rootAfterRemove, "a/b/d.txt") + err = treeOp.RemoveEntry(ctx, "a/b/d.txt") require.NoError(t, err) - require.Equal(t, "", rootAfterRemoveAll.Hash.Hex()) + require.Equal(t, "", hash.Hash(treeOp.Root().Hash()).Hex()) } func TestRemoveEntry(t *testing.T) { @@ -129,7 +133,8 @@ func TestRemoveEntry(t *testing.T) { adapter := mem.New(ctx) objRepo := models.NewObjectRepo(db) - treeOp := NewTreeOp(objRepo) + treeOp, err := NewWorkTree(ctx, objRepo, EmptyDirEntry) + require.NoError(t, err) binary := []byte("Build simple, secure, scalable systems with Go") bLen := int64(len(binary)) @@ -137,9 +142,9 @@ func TestRemoveEntry(t *testing.T) { blob, err := treeOp.WriteBlob(ctx, adapter, r, bLen, block.PutOpts{}) require.NoError(t, err) - root, err := treeOp.AddLeaf(ctx, EmptyRoot, "a/b/c.txt", blob) + err = treeOp.AddLeaf(ctx, "a/b/c.txt", blob) require.NoError(t, err) - require.Equal(t, "3bf643c30934d121ee45d413b165f135", root.Hash.Hex()) + require.Equal(t, "3bf643c30934d121ee45d413b165f135", hash.Hash(treeOp.Root().Hash()).Hex()) //update path binary = []byte(`“At the time, no single team member knew Go, but within a month, everyone was writing in Go and we were building out the endpoints. ”`) @@ -149,11 +154,11 @@ func TestRemoveEntry(t *testing.T) { require.NoError(t, err) //add another branch - root, err = treeOp.AddLeaf(ctx, root, "a/b/d.txt", blob) + err = treeOp.AddLeaf(ctx, "a/b/d.txt", blob) require.NoError(t, err) - require.Equal(t, "81173b4a85cc5643feacd38b975e61a1", root.Hash.Hex()) + require.Equal(t, "81173b4a85cc5643feacd38b975e61a1", hash.Hash(treeOp.Root().Hash()).Hex()) - rootAfterRemoveAll, err := treeOp.RemoveEntry(ctx, root, "a/b") + err = treeOp.RemoveEntry(ctx, "a/b") require.NoError(t, err) - require.Equal(t, "", rootAfterRemoveAll.Hash.Hex()) + require.Equal(t, "", hash.Hash(treeOp.Root().Hash()).Hex()) } From c75dac0d991e2dfde209da0362be2cbc83b0dbc0 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Sun, 10 Dec 2023 21:52:35 +0800 Subject: [PATCH 055/210] feat: implement three way merge --- versionmgr/changes.go | 214 +++++++++++++++++++++++ versionmgr/commit.go | 192 ++++++++++++++------ versionmgr/commit_node.go | 60 ++++--- versionmgr/commit_test.go | 36 +++- versionmgr/commit_walker.go | 14 +- versionmgr/commit_walker_bfs.go | 6 +- versionmgr/commit_walker_bfs_filtered.go | 6 +- versionmgr/merge_base.go | 26 +-- versionmgr/merge_base_test.go | 10 +- versionmgr/worktree.go | 26 +++ 10 files changed, 477 insertions(+), 113 deletions(-) create mode 100644 versionmgr/changes.go diff --git a/versionmgr/changes.go b/versionmgr/changes.go new file mode 100644 index 00000000..8198574e --- /dev/null +++ b/versionmgr/changes.go @@ -0,0 +1,214 @@ +package versionmgr + +import ( + "bytes" + "fmt" + "io" + "sort" + "strings" + + "github.com/go-git/go-git/v5/utils/merkletrie" +) + +type Change struct { + merkletrie.Change +} + +func (c Change) Path() string { + action, err := c.Action() + if err != nil { + panic(err) + } + + var path string + if action == merkletrie.Delete { + path = c.From.String() + } else { + path = c.To.String() + } + + return path +} + +type Changes struct { + changes []Change + idx int +} + +func NewChanges(changes []Change) *Changes { + sort.Slice(changes, func(i, j int) bool { + return strings.Compare(changes[i].Path(), changes[j].Path()) > 0 //i > j + }) + return &Changes{changes: changes, idx: -1} +} + +func (c Changes) Num() int { + return len(c.changes) +} + +func (c Changes) Changes() []Change { + return c.changes +} + +func (c Changes) Next() (Change, error) { + if c.idx == len(c.changes)-1 { + c.idx++ + return c.changes[c.idx], nil + } + return Change{}, io.EOF +} + +func (c Changes) Has() bool { + return c.idx == len(c.changes)-1 +} + +func (c Changes) Back() { + if c.idx > -1 { + c.idx-- + } +} + +func (c Changes) Reset() { + c.idx = -1 +} + +func newChanges(mChanges merkletrie.Changes) *Changes { + changes := make([]Change, len(mChanges)) + for index, change := range mChanges { + changes[index] = Change{change} + } + return NewChanges(changes) +} + +type ConflictResolver func(base *Change, merged *Change) (*Change, error) + +type ChangesMergeIter struct { + baseChanges *Changes + mergerChanges *Changes + resolver ConflictResolver +} + +func NewChangesMergeIter(baseChanges *Changes, mergerChanges *Changes, resolver ConflictResolver) *ChangesMergeIter { + return &ChangesMergeIter{baseChanges: baseChanges, mergerChanges: mergerChanges, resolver: resolver} +} + +func (cw ChangesMergeIter) Has() bool { + return cw.baseChanges.Has() || cw.mergerChanges.Has() +} + +func (cw ChangesMergeIter) Reset() { + cw.baseChanges.Reset() + cw.mergerChanges.Reset() +} +func (cw ChangesMergeIter) Next() (*Change, error) { + baseNode, baseErr := cw.baseChanges.Next() + if baseErr != nil && baseErr != io.EOF { + return nil, baseErr + } + + mergeNode, mergerError := cw.mergerChanges.Next() + if mergerError != nil && mergerError != io.EOF { + return nil, mergerError + } + + if baseErr == io.EOF && mergerError == io.EOF { + return nil, io.EOF + } + + if baseErr == io.EOF { + return &mergeNode, nil + } + + if mergerError == io.EOF { + return &baseNode, nil + } + + compare := strings.Compare(baseNode.Path(), mergeNode.Path()) + if compare < 0 { + //only merger change + cw.baseChanges.Back() + return &mergeNode, nil + } else if compare == 0 { + + //both change + if baseNode.From == nil && mergeNode.From == nil { + //both delete + return &baseNode, nil + } + if bytes.Equal(baseNode.From.Hash(), mergeNode.From.Hash()) { + //both modify/add apply any + return &baseNode, nil + } + //conflict + if cw.resolver != nil { + resolveResult, err := cw.resolver(&baseNode, &mergeNode) + if err != nil { + return nil, err + } + return resolveResult, nil + } + return nil, fmt.Errorf("path %s confilict %w", mergeNode.Path(), ErrConflict) + } else { + //only base change + cw.mergerChanges.Back() + return &baseNode, nil + } +} + +func (cw ChangesMergeIter) compareBothChange(base, merge *Change) (*Change, error) { + baseAction, err := base.Action() + if err != nil { + return nil, err + } + mergeAction, err := merge.Action() + if err != nil { + return nil, err + } + switch baseAction { + case merkletrie.Insert: + switch mergeAction { + case merkletrie.Delete: + return cw.resolver(base, merge) + case merkletrie.Modify: + return nil, fmt.Errorf("%s merge should never be Modify while the other diff is Insert, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues", base.Path()) + case merkletrie.Insert: + if bytes.Equal(base.From.Hash(), merge.From.Hash()) { + return base, nil + } + return cw.resolver(base, merge) + } + case merkletrie.Delete: + switch mergeAction { + case merkletrie.Delete: + return base, nil + case merkletrie.Insert: + return nil, fmt.Errorf("%s merge should never be Insert while the other diff is Delete, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues", base.Path()) + case merkletrie.Modify: + return cw.resolver(base, merge) + } + case merkletrie.Modify: + switch mergeAction { + case merkletrie.Insert: + return nil, fmt.Errorf("%s merge should never be Insert while the other diff is Modify, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues", base.Path()) + case merkletrie.Delete: + return cw.resolver(base, merge) + case merkletrie.Modify: + if bytes.Equal(base.From.Hash(), merge.From.Hash()) { + return base, nil + } + return cw.resolver(base, merge) + } + } + return nil, fmt.Errorf("not match action") +} + +func (cw ChangesMergeIter) resolveConflict(base, merge *Change) (*Change, error) { + if cw.resolver != nil { + resolveResult, err := cw.resolver(base, merge) + if err != nil { + return nil, err + } + return resolveResult, nil + } + return nil, fmt.Errorf("path %s confilict %w", merge.Path(), ErrConflict) +} diff --git a/versionmgr/commit.go b/versionmgr/commit.go index 385e4fb8..736ddcc6 100644 --- a/versionmgr/commit.go +++ b/versionmgr/commit.go @@ -3,6 +3,8 @@ package versionmgr import ( "bytes" "context" + "errors" + "fmt" "time" "github.com/go-git/go-git/v5/utils/merkletrie" @@ -18,51 +20,57 @@ import ( "github.com/jiaozifs/jiaozifs/models" ) +var ( + ErrConflict = errors.New("conflict dected but not found resolver") +) + type CommitOp struct { - User models.IUserRepo - Object models.IObjectRepo - Wip models.IWipRepo - Ref models.IRefRepo + commit *models.Commit + + user models.IUserRepo + object models.IObjectRepo + wip models.IWipRepo } -func NewCommitOp(repo models.IRepo) *CommitOp { +func NewCommitOp(repo models.IRepo, commit *models.Commit) *CommitOp { return &CommitOp{ - User: repo.UserRepo(), - Object: repo.ObjectRepo(), - Wip: repo.WipRepo(), - Ref: repo.RefRepo(), + commit: commit, + user: repo.UserRepo(), + object: repo.ObjectRepo(), + wip: repo.WipRepo(), } } -func (commitOp *CommitOp) AddCommit(ctx context.Context, refID, committerID, wipID uuid.UUID, msg string) (*models.Commit, error) { - wip, err := commitOp.Wip.Get(ctx, &models.GetWipParam{ +func (commitOp *CommitOp) Commit() *models.Commit { + return commitOp.commit +} + +func (commitOp *CommitOp) AddCommit(ctx context.Context, committerID, wipID uuid.UUID, msg string) (*CommitOp, error) { + wip, err := commitOp.wip.Get(ctx, &models.GetWipParam{ ID: wipID, }) if err != nil { return nil, err } - committer, err := commitOp.User.Get(ctx, &models.GetUserParam{ + committer, err := commitOp.user.Get(ctx, &models.GetUserParam{ ID: committerID, }) if err != nil { return nil, err } - creator, err := commitOp.User.Get(ctx, &models.GetUserParam{ + creator, err := commitOp.user.Get(ctx, &models.GetUserParam{ ID: wip.CreateID, }) if err != nil { return nil, err } - ref, err := commitOp.Ref.Get(ctx, &models.GetRefParams{ - ID: refID, - }) - if err != nil { - return nil, err + parentHash := []hash.Hash{} + if commitOp.commit != nil { + parentHash = []hash.Hash{commitOp.commit.Hash} } - commit := &models.Commit{ Hash: nil, Type: models.CommitObject, @@ -79,7 +87,7 @@ func (commitOp *CommitOp) AddCommit(ctx context.Context, refID, committerID, wip MergeTag: "", Message: msg, TreeHash: wip.CurrentTree, - ParentHashes: []hash.Hash{ref.CommitHash}, + ParentHashes: parentHash, CreatedAt: time.Now(), UpdatedAt: time.Now(), } @@ -88,83 +96,157 @@ func (commitOp *CommitOp) AddCommit(ctx context.Context, refID, committerID, wip return nil, err } commit.Hash = commitHash - _, err = commitOp.Object.Insert(ctx, commit.Object()) + _, err = commitOp.object.Insert(ctx, commit.Object()) if err != nil { return nil, err } - ref.CommitHash = commitHash - err = commitOp.Ref.UpdateCommitHash(ctx, ref.ID, commitHash) + return &CommitOp{ + commit: commit, + user: commitOp.user, + object: commitOp.object, + wip: commitOp.wip, + }, nil +} + +func (commitOp *CommitOp) DiffCommit(ctx context.Context, toCommitID hash.Hash) (*Changes, error) { + fromNode, err := NewTreeNode(ctx, models.TreeEntry{ + Name: "", + Mode: filemode.Dir, + Hash: commitOp.commit.TreeHash, + }, commitOp.object) if err != nil { return nil, err } - err = commitOp.Wip.UpdateState(ctx, wipID, models.Completed) + + toCommit, err := commitOp.object.Commit(ctx, toCommitID) + if err != nil { + return nil, err + } + toNode, err := NewTreeNode(ctx, models.TreeEntry{ + Name: "", + Mode: filemode.Dir, + Hash: toCommit.TreeHash, + }, commitOp.object) if err != nil { return nil, err } - return commit, nil -} -func (commitOp *CommitOp) DiffCommit(ctx context.Context, baseCommitID, toCommitID hash.Hash) (merkletrie.Changes, error) { - baseCommit, err := commitOp.Object.Commit(ctx, baseCommitID) + changes, err := merkletrie.DiffTreeContext(ctx, fromNode, toNode, func(a, b noder.Hasher) bool { + return bytes.Equal(a.Hash(), b.Hash()) + }) if err != nil { return nil, err } + return newChanges(changes), nil +} - toCommit, err := commitOp.Object.Commit(ctx, toCommitID) +func (commitOp *CommitOp) Merge(ctx context.Context, mergerID uuid.UUID, toMergeCommitHash hash.Hash, msg string) (*models.Commit, error) { + merger, err := commitOp.user.Get(ctx, &models.GetUserParam{ + ID: mergerID, + }) if err != nil { return nil, err } - fromNode, err := NewTreeNode(ctx, models.TreeEntry{ - Name: "", - Mode: filemode.Dir, - Hash: baseCommit.TreeHash, - }, commitOp.Object) + toMergeCommit, err := commitOp.object.Commit(ctx, toMergeCommitHash) if err != nil { return nil, err } - toNode, err := NewTreeNode(ctx, models.TreeEntry{ - Name: "", - Mode: filemode.Dir, - Hash: toCommit.TreeHash, - }, commitOp.Object) + //find accesstor + baseCommitNode := NewCommitNode(ctx, commitOp.commit, commitOp.object) + toMergeCommitNode := NewCommitNode(ctx, toMergeCommit, commitOp.object) + bestAncestor, err := baseCommitNode.MergeBase(toMergeCommitNode) if err != nil { return nil, err } - return merkletrie.DiffTreeContext(ctx, fromNode, toNode, func(a, b noder.Hasher) bool { - return bytes.Equal(a.Hash(), b.Hash()) - }) -} + //Fast forward merge + //3 way merge -func (commitOp *CommitOp) Merge(ctx context.Context, mergerID, baseRefID, mergeRefID uuid.UUID) (*models.Commit, error) { - _, err := commitOp.User.Get(ctx, &models.GetUserParam{ - ID: mergerID, - }) + if len(bestAncestor) == 0 { + return nil, fmt.Errorf("no common ancesstor find") + } + + bestCommit := bestAncestor[0] + if len(bestAncestor) > 1 { + //merge cross merge create virtual commit + firstCommit := &CommitOp{ + commit: bestAncestor[0].Commit(), + user: commitOp.user, + object: commitOp.object, + wip: commitOp.wip, + } + virtualCommit, err := firstCommit.Merge(ctx, mergerID, bestAncestor[1].Commit().Hash, "") + if err != nil { + return nil, err + } + + bestCommit = NewCommitNode(ctx, virtualCommit, commitOp.object) + } + + baseDiff, err := commitOp.DiffCommit(ctx, bestCommit.Commit().Hash) if err != nil { return nil, err } - baseRef, err := commitOp.Ref.Get(ctx, &models.GetRefParams{ - ID: baseRefID, - }) + mergeCommitOp := &CommitOp{ + commit: toMergeCommit, + user: commitOp.user, + object: commitOp.object, + wip: commitOp.wip, + } + mergeDiff, err := mergeCommitOp.DiffCommit(ctx, bestCommit.Commit().Hash) if err != nil { return nil, err } - mergeRef, err := commitOp.Ref.Get(ctx, &models.GetRefParams{ - ID: mergeRefID, - }) + //merge diff + cmw := NewChangesMergeIter(baseDiff, mergeDiff, nil) + workTree, err := NewWorkTree(ctx, commitOp.object, models.NewRootTreeEntry(bestCommit.Commit().TreeHash)) if err != nil { return nil, err } - _, err = commitOp.DiffCommit(ctx, baseRef.CommitHash, mergeRef.CommitHash) + for cmw.Has() { + change, err := cmw.Next() + if err != nil { + return nil, err + } + //apply change + err = workTree.ApplyOneChange(ctx, change) + if err != nil { + return nil, err + } + } + + author := models.Signature{ + Name: merger.Name, + Email: merger.Email, + When: time.Now(), + } + + mergeCommit := &models.Commit{ + Type: models.CommitObject, + Author: author, + Committer: author, + MergeTag: "", + Message: msg, + TreeHash: workTree.Root().Hash(), + ParentHashes: []hash.Hash{commitOp.commit.Hash, toMergeCommitHash}, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + hash, err := mergeCommit.GetHash() if err != nil { return nil, err } + mergeCommit.Hash = hash - return nil, nil + mergeCommitObject, err := commitOp.object.Insert(ctx, mergeCommit.Object()) + if err != nil { + return nil, err + } + return mergeCommitObject.Commit(), nil } diff --git a/versionmgr/commit_node.go b/versionmgr/commit_node.go index 3360010e..a3236c3f 100644 --- a/versionmgr/commit_node.go +++ b/versionmgr/commit_node.go @@ -18,64 +18,80 @@ var ( ) type CommitNode struct { - Ctx context.Context - models.Commit - Object models.IObjectRepo + ctx context.Context + commit *models.Commit + object models.IObjectRepo +} + +func NewCommitNode(ctx context.Context, commit *models.Commit, object models.IObjectRepo) *CommitNode { + return &CommitNode{ctx: ctx, commit: commit, object: object} +} + +func (c *CommitNode) Ctx() context.Context { + return c.ctx +} + +func (c *CommitNode) Commit() *models.Commit { + return c.commit +} + +func (c *CommitNode) Object() models.IObjectRepo { + return c.object } // Tree returns the Tree from the commit. func (c *CommitNode) Tree() (*TreeNode, error) { - treeNode, err := c.Object.TreeNode(c.Ctx, c.TreeHash) + treeNode, err := c.object.TreeNode(c.ctx, c.commit.TreeHash) if err != nil { return nil, err } - return NewTreeNode(c.Ctx, models.TreeEntry{ + return NewTreeNode(c.ctx, models.TreeEntry{ Name: "", Mode: filemode.Dir, Hash: treeNode.Hash, - }, c.Object) + }, c.object) } // Parents return a CommitIter to the parent Commits. func (c *CommitNode) Parents() ([]*CommitNode, error) { - parentNodes := make([]*CommitNode, len(c.ParentHashes)) - for _, hash := range c.ParentHashes { - commit, err := c.Object.Commit(c.Ctx, hash) + parentNodes := make([]*CommitNode, len(c.commit.ParentHashes)) + for _, hash := range c.commit.ParentHashes { + commit, err := c.object.Commit(c.ctx, hash) if err != nil { return nil, err } parentNodes = append(parentNodes, &CommitNode{ - Ctx: c.Ctx, - Commit: *commit, - Object: c.Object, + ctx: c.ctx, + commit: commit, + object: c.object, }) } return parentNodes, nil } func (c *CommitNode) GetCommit(hash hash.Hash) (*CommitNode, error) { - commit, err := c.Object.Commit(c.Ctx, hash) + commit, err := c.object.Commit(c.ctx, hash) if err != nil { return nil, err } return &CommitNode{ - Ctx: c.Ctx, - Commit: *commit, - Object: c.Object, + ctx: c.ctx, + commit: commit, + object: c.object, }, nil } func (c *CommitNode) GetCommits(hashes []hash.Hash) ([]*CommitNode, error) { commits := make([]*CommitNode, len(hashes)) for i, hash := range hashes { - commit, err := c.Object.Commit(c.Ctx, hash) + commit, err := c.object.Commit(c.ctx, hash) if err != nil { return nil, err } commits[i] = &CommitNode{ - Ctx: c.Ctx, - Commit: *commit, - Object: c.Object, + ctx: c.ctx, + commit: commit, + object: c.object, } } return commits, nil @@ -121,3 +137,7 @@ func (a arraryCommitIter) ForEach(f func(*CommitNode) error) error { } return nil } + +func (a arraryCommitIter) Has() bool { + return a.idx == len(a.commits)-1 +} diff --git a/versionmgr/commit_test.go b/versionmgr/commit_test.go index 0feacc5d..ad769237 100644 --- a/versionmgr/commit_test.go +++ b/versionmgr/commit_test.go @@ -39,7 +39,6 @@ b/e.txt |e2 require.NoError(t, err) root2, err := makeRoot(ctx, repo.ObjectRepo(), testData2) require.NoError(t, err) - op := NewCommitOp(repo) user, err := makeUser(ctx, repo.UserRepo(), "admin") require.NoError(t, err) @@ -47,23 +46,26 @@ b/e.txt |e2 project, err := makeRepository(ctx, repo.RepositoryRepo(), "testproject") require.NoError(t, err) + //base branch baseRef, err := makeRef(ctx, repo.RefRepo(), "feat/base", project.ID, hash.Hash("a")) require.NoError(t, err) baseWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseRef.ID, root1.Hash) require.NoError(t, err) - baseCommit, err := op.AddCommit(ctx, baseRef.ID, user.ID, baseWip.ID, "base commit") + + baseCommit, err := NewCommitOp(repo, nil).AddCommit(ctx, user.ID, baseWip.ID, "base commit") require.NoError(t, err) + //toMerge branch mergeRef, err := makeRef(ctx, repo.RefRepo(), "feat/merge", project.ID, hash.Hash("a")) require.NoError(t, err) mergeWip, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeRef.ID, root2.Hash) require.NoError(t, err) - mergeCommit, err := op.AddCommit(ctx, mergeRef.ID, user.ID, mergeWip.ID, "merge commit") + mergeCommit, err := NewCommitOp(repo, nil).AddCommit(ctx, user.ID, mergeWip.ID, "merge commit") require.NoError(t, err) - changes, err := op.DiffCommit(ctx, baseCommit.Hash, mergeCommit.Hash) + changes, err := baseCommit.DiffCommit(ctx, mergeCommit.Commit().Hash) require.NoError(t, err) - require.Len(t, changes, 3) + require.Len(t, changes.Num(), 3) } func makeUser(ctx context.Context, userRepo models.IUserRepo, name string) (*models.User, error) { @@ -93,6 +95,30 @@ func makeRepository(ctx context.Context, repoRepo models.IRepositoryRepo, name s return repoRepo.Insert(ctx, user) } +func makeCommit(ctx context.Context, commitRepo models.IObjectRepo, treeHash hash.Hash, msg string, parentsHash ...hash.Hash) (*models.Commit, error) { + commit := &models.Commit{ + Hash: hash.Hash("mock"), + Type: models.CommitObject, + Author: models.Signature{ + Name: "admin", + Email: "xxx@gg.com", + When: time.Time{}, + }, + Committer: models.Signature{ + Name: "admin", + Email: "xxx@gg.com", + When: time.Time{}, + }, + TreeHash: treeHash, + ParentHashes: parentsHash, + Message: msg, + } + obj, err := commitRepo.Insert(ctx, commit.Object()) + if err != nil { + return nil, err + } + return obj.Commit(), nil +} func makeRef(ctx context.Context, refRepo models.IRefRepo, name string, repoID uuid.UUID, commitHash hash.Hash) (*models.Ref, error) { ref := &models.Ref{ RepositoryID: repoID, diff --git a/versionmgr/commit_walker.go b/versionmgr/commit_walker.go index 6646c4af..000aea44 100644 --- a/versionmgr/commit_walker.go +++ b/versionmgr/commit_walker.go @@ -65,13 +65,13 @@ func (w *commitPreIterator) Next() (*CommitNode, error) { } } - if w.seen[c.Hash.Hex()] || w.seenExternal[c.Hash.Hex()] { + if w.seen[c.Commit().Hash.Hex()] || w.seenExternal[c.Commit().Hash.Hex()] { continue } - w.seen[c.Hash.Hex()] = true + w.seen[c.Commit().Hash.Hex()] = true - if c.NumParents() > 0 { + if c.Commit().NumParents() > 0 { commitIter, err := filteredParentIter(c, w.seen) if err != nil { return nil, err @@ -85,7 +85,7 @@ func (w *commitPreIterator) Next() (*CommitNode, error) { func filteredParentIter(c *CommitNode, seen map[string]bool) (CommitIter, error) { var hashes []hash.Hash - for _, h := range c.ParentHashes { + for _, h := range c.Commit().ParentHashes { if !seen[h.Hex()] { hashes = append(hashes, h) } @@ -109,7 +109,7 @@ func (w *commitPreIterator) ForEach(cb func(*CommitNode) error) error { } err = cb(c) - if err == storer.ErrStop { + if err == ErrStop { break } if err != nil { @@ -151,11 +151,11 @@ func (w *commitPostIterator) Next() (*CommitNode, error) { c := w.stack[len(w.stack)-1] w.stack = w.stack[:len(w.stack)-1] - if w.seen[c.Hash.Hex()] { + if w.seen[c.Commit().Hash.Hex()] { continue } - w.seen[c.Hash.Hex()] = true + w.seen[c.Commit().Hash.Hex()] = true parentCommits, err := c.Parents() if err != nil { diff --git a/versionmgr/commit_walker_bfs.go b/versionmgr/commit_walker_bfs.go index 58840a8c..d83e33bc 100644 --- a/versionmgr/commit_walker_bfs.go +++ b/versionmgr/commit_walker_bfs.go @@ -59,13 +59,13 @@ func (w *bfsCommitIterator) Next() (*CommitNode, error) { c = w.queue[0] w.queue = w.queue[1:] - if w.seen[c.Hash.Hex()] || w.seenExternal[c.Hash.Hex()] { + if w.seen[c.Commit().Hash.Hex()] || w.seenExternal[c.Commit().Hash.Hex()] { continue } - w.seen[c.Hash.Hex()] = true + w.seen[c.Commit().Hash.Hex()] = true - for _, h := range c.ParentHashes { + for _, h := range c.Commit().ParentHashes { err := w.appendHash(c, h) if err != nil { return nil, err diff --git a/versionmgr/commit_walker_bfs_filtered.go b/versionmgr/commit_walker_bfs_filtered.go index 9729c89f..a30c76ef 100644 --- a/versionmgr/commit_walker_bfs_filtered.go +++ b/versionmgr/commit_walker_bfs_filtered.go @@ -72,10 +72,10 @@ func (w *filterCommitIter) Next() (*CommitNode, error) { return nil, w.close(err) } - w.visited[commit.Hash.Hex()] = struct{}{} + w.visited[commit.Commit().Hash.Hex()] = struct{}{} if !w.isLimit(commit) { - err = w.addToQueue(commit, commit.ParentHashes...) + err = w.addToQueue(commit, commit.Commit().ParentHashes...) if err != nil { return nil, w.close(err) } @@ -148,7 +148,7 @@ func (w *filterCommitIter) popNewFromQueue() (*CommitNode, error) { first = w.queue[0] w.queue = w.queue[1:] - if _, ok := w.visited[first.Hash.Hex()]; ok { + if _, ok := w.visited[first.Commit().Hash.Hex()]; ok { continue } diff --git a/versionmgr/merge_base.go b/versionmgr/merge_base.go index 801f9f95..0aabcbb8 100644 --- a/versionmgr/merge_base.go +++ b/versionmgr/merge_base.go @@ -48,7 +48,7 @@ func (c *CommitNode) IsAncestor(other *CommitNode) (bool, error) { found := false iter := NewCommitPreorderIter(other, nil, nil) err := iter.ForEach(func(comm *CommitNode) error { - if !bytes.Equal(comm.Hash, c.Hash) { + if !bytes.Equal(comm.Commit().Hash, c.Commit().Hash) { return nil } @@ -63,18 +63,18 @@ func (c *CommitNode) IsAncestor(other *CommitNode) (bool, error) { // excluded one is not one of them. It returns errIsReachable if the excluded commit // is ancestor of the starting, or another error if the history is not traversable. func ancestorsIndex(excluded, starting *CommitNode) (map[string]struct{}, error) { - if bytes.Equal(excluded.Hash, starting.Hash) { + if bytes.Equal(excluded.Commit().Hash, starting.Commit().Hash) { return nil, errIsReachable } startingHistory := map[string]struct{}{} startingIter := NewCommitIterBSF(starting, nil, nil) err := startingIter.ForEach(func(commit *CommitNode) error { - if bytes.Equal(commit.Hash, excluded.Hash) { + if bytes.Equal(commit.Commit().Hash, excluded.Commit().Hash) { return errIsReachable } - startingHistory[commit.Hash.Hex()] = struct{}{} + startingHistory[commit.Commit().Hash.Hex()] = struct{}{} return nil }) @@ -94,7 +94,7 @@ func Independents(commits []*CommitNode) ([]*CommitNode, error) { seen := map[string]struct{}{} var isLimit CommitFilter = func(commit *CommitNode) bool { - _, ok := seen[commit.Hash.Hex()] + _, ok := seen[commit.Commit().Hash.Hex()] return ok } @@ -109,7 +109,7 @@ func Independents(commits []*CommitNode) ([]*CommitNode, error) { fromHistoryIter := NewFilterCommitIter(from, nil, &isLimit) err := fromHistoryIter.ForEach(func(fromAncestor *CommitNode) error { for _, other := range others { - if bytes.Equal(fromAncestor.Hash, other.Hash) { + if bytes.Equal(fromAncestor.Commit().Hash, other.Commit().Hash) { candidates = remove(candidates, other) others = remove(others, other) } @@ -119,7 +119,7 @@ func Independents(commits []*CommitNode) ([]*CommitNode, error) { return storer.ErrStop } - seen[fromAncestor.Hash.Hex()] = struct{}{} + seen[fromAncestor.Commit().Hash.Hex()] = struct{}{} return nil }) @@ -150,7 +150,7 @@ func sortByCommitDateDesc(commits ...*CommitNode) []*CommitNode { sorted := make([]*CommitNode, len(commits)) copy(sorted, commits) sort.Slice(sorted, func(i, j int) bool { - return sorted[i].Committer.When.After(sorted[j].Committer.When) + return sorted[i].Commit().Committer.When.After(sorted[j].Commit().Committer.When) }) return sorted @@ -159,7 +159,7 @@ func sortByCommitDateDesc(commits ...*CommitNode) []*CommitNode { // indexOf returns the first position where target was found in the passed commits func indexOf(commits []*CommitNode, target *CommitNode) int { for i, commit := range commits { - if bytes.Equal(target.Hash, commit.Hash) { + if bytes.Equal(target.Commit().Hash, commit.Commit().Hash) { return i } } @@ -172,7 +172,7 @@ func remove(commits []*CommitNode, toDelete *CommitNode) []*CommitNode { res := make([]*CommitNode, len(commits)) j := 0 for _, commit := range commits { - if bytes.Equal(commit.Hash, toDelete.Hash) { + if bytes.Equal(commit.Commit().Hash, toDelete.Commit().Hash) { continue } @@ -189,11 +189,11 @@ func removeDuplicated(commits []*CommitNode) []*CommitNode { res := make([]*CommitNode, len(commits)) j := 0 for _, commit := range commits { - if _, ok := seen[commit.Hash.Hex()]; ok { + if _, ok := seen[commit.Commit().Hash.Hex()]; ok { continue } - seen[commit.Hash.Hex()] = struct{}{} + seen[commit.Commit().Hash.Hex()] = struct{}{} res[j] = commit j++ } @@ -205,7 +205,7 @@ func removeDuplicated(commits []*CommitNode) []*CommitNode { // if the commit is in the passed index. func isInIndexCommitFilter(index map[string]struct{}) CommitFilter { return func(c *CommitNode) bool { - _, ok := index[c.Hash.Hex()] + _, ok := index[c.Commit().Hash.Hex()] return ok } } diff --git a/versionmgr/merge_base_test.go b/versionmgr/merge_base_test.go index f267efbd..af66029f 100644 --- a/versionmgr/merge_base_test.go +++ b/versionmgr/merge_base_test.go @@ -45,7 +45,7 @@ e|b ancestorNode, err := baseCommit.MergeBase(mergeCommit) require.NoError(t, err) require.Len(t, ancestorNode, 1) - require.Equal(t, "a", string(ancestorNode[0].Hash)) + require.Equal(t, "a", string(ancestorNode[0].Commit().Hash)) }) t.Run("multiple merge", func(t *testing.T) { @@ -54,7 +54,7 @@ e|b ancestorNode, err := baseCommit.MergeBase(mergeCommit) require.NoError(t, err) require.Len(t, ancestorNode, 1) - require.Equal(t, "b", string(ancestorNode[0].Hash)) + require.Equal(t, "b", string(ancestorNode[0].Commit().Hash)) }) } @@ -68,11 +68,7 @@ func loadCommitTestData(ctx context.Context, objRepo models.IObjectRepo, testDat commitData := strings.Split(strings.TrimSpace(line), "|") hashName := strings.TrimSpace(commitData[0]) commit := newCommit(hashName, strings.Split(commitData[1], ",")) - commitMap[hashName] = &CommitNode{ - Ctx: ctx, - Commit: *commit, - Object: objRepo, - } + commitMap[hashName] = NewCommitNode(ctx, commit, objRepo) _, err := objRepo.Insert(ctx, commit.Object()) if err != nil { return nil, err diff --git a/versionmgr/worktree.go b/versionmgr/worktree.go index 3e043bef..be1ad8b2 100644 --- a/versionmgr/worktree.go +++ b/versionmgr/worktree.go @@ -10,6 +10,8 @@ import ( "strings" "time" + "github.com/go-git/go-git/v5/utils/merkletrie" + "github.com/jiaozifs/jiaozifs/block" "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/jiaozifs/jiaozifs/utils/pathutil" @@ -483,3 +485,27 @@ func (workTree *WorkTree) Ls(ctx context.Context, fullPath string) ([]models.Tre return lastNode.Node().SubObjects, nil } + +func (workTree *WorkTree) ApplyOneChange(ctx context.Context, change *Change) error { + action, err := change.Action() + if err != nil { + return err + } + switch action { + case merkletrie.Insert: + blob, err := workTree.object.Blob(ctx, change.From.Hash()) + if err != nil { + return err + } + return workTree.AddLeaf(ctx, change.From.String(), blob) + case merkletrie.Delete: + return workTree.RemoveEntry(ctx, change.From.String()) + case merkletrie.Modify: + blob, err := workTree.object.Blob(ctx, change.From.Hash()) + if err != nil { + return err + } + return workTree.ReplaceLeaf(ctx, change.From.String(), blob) + } + return fmt.Errorf("unexpect change action: %s", action) +} From 5a8ed4cfdb27a73e53698048b8a48f6162a2404c Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Sun, 10 Dec 2023 22:10:17 +0800 Subject: [PATCH 056/210] feat: add fast-forward merge --- versionmgr/commit.go | 44 ++++++++++++++++++++++++++--------- versionmgr/merge_base_test.go | 18 +++++++++++--- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/versionmgr/commit.go b/versionmgr/commit.go index 736ddcc6..2d87d160 100644 --- a/versionmgr/commit.go +++ b/versionmgr/commit.go @@ -8,19 +8,16 @@ import ( "time" "github.com/go-git/go-git/v5/utils/merkletrie" - - "github.com/jiaozifs/jiaozifs/models/filemode" - "github.com/go-git/go-git/v5/utils/merkletrie/noder" - - "github.com/jiaozifs/jiaozifs/utils/hash" - "github.com/google/uuid" - + logging "github.com/ipfs/go-log/v2" "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/models/filemode" + "github.com/jiaozifs/jiaozifs/utils/hash" ) var ( + commitLog = logging.Logger("commit") ErrConflict = errors.New("conflict dected but not found resolver") ) @@ -155,16 +152,41 @@ func (commitOp *CommitOp) Merge(ctx context.Context, mergerID uuid.UUID, toMerge } //find accesstor - baseCommitNode := NewCommitNode(ctx, commitOp.commit, commitOp.object) + baseCommitNode := NewCommitNode(ctx, commitOp.Commit(), commitOp.object) toMergeCommitNode := NewCommitNode(ctx, toMergeCommit, commitOp.object) + + { + //do nothing while merge is ancestor of base + mergeIsAncestorOfBase, err := toMergeCommitNode.IsAncestor(baseCommitNode) + if err != nil { + return nil, err + } + + if mergeIsAncestorOfBase { + commitLog.Warnf("merge commit %s is ancestor of base commit %s", toMergeCommitHash, commitOp.Commit().Hash) + return commitOp.Commit(), nil + } + } + + { + //try fast-forward merge no need to create new commit node + baseIsAncestorOfMerge, err := baseCommitNode.IsAncestor(toMergeCommitNode) + if err != nil { + return nil, err + } + + if baseIsAncestorOfMerge { + commitLog.Warnf("base commit %s is ancestor of merge commit %s", toMergeCommitHash, commitOp.Commit().Hash) + return toMergeCommit, nil + } + } + + // three way merge bestAncestor, err := baseCommitNode.MergeBase(toMergeCommitNode) if err != nil { return nil, err } - //Fast forward merge - //3 way merge - if len(bestAncestor) == 0 { return nil, fmt.Errorf("no common ancesstor find") } diff --git a/versionmgr/merge_base_test.go b/versionmgr/merge_base_test.go index af66029f..c8a3e66a 100644 --- a/versionmgr/merge_base_test.go +++ b/versionmgr/merge_base_test.go @@ -14,7 +14,7 @@ import ( "github.com/jiaozifs/jiaozifs/testhelper" ) -func TestCommitNode_MergeBase(t *testing.T) { +func TestCommitNodeMergeBase(t *testing.T) { ctx := context.Background() postgres, _, db := testhelper.SetupDatabase(ctx, t) defer postgres.Stop() //nolint @@ -23,7 +23,7 @@ func TestCommitNode_MergeBase(t *testing.T) { //mock data // | -> c ------- // | | - //a ------> b ------d----f---------merge? + //a ------> b ------d--f1-f2---f--merge? // | | // | ----------------->e---- @@ -32,7 +32,9 @@ a| b|a c|a d|b,c -f|d +f1|d +f2|f1 +f|f2 e|b ` commitMap, err := loadCommitTestData(ctx, objRepo, testData) @@ -48,6 +50,16 @@ e|b require.Equal(t, "a", string(ancestorNode[0].Commit().Hash)) }) + t.Run("fast forward", func(t *testing.T) { + //simple + baseCommit := commitMap["f"] + mergeCommit := commitMap["f1"] + ancestorNode, err := baseCommit.MergeBase(mergeCommit) + require.NoError(t, err) + require.Len(t, ancestorNode, 1) + require.Equal(t, "f1", string(ancestorNode[0].Commit().Hash)) + }) + t.Run("multiple merge", func(t *testing.T) { baseCommit := commitMap["f"] mergeCommit := commitMap["e"] From 96f5ebcdd480f9474608b6fb9963b8b523821324 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Sun, 10 Dec 2023 22:19:30 +0800 Subject: [PATCH 057/210] feat: make lint happy --- versionmgr/changes.go | 69 ++++++++++++++------------------------- versionmgr/commit_test.go | 3 +- 2 files changed, 27 insertions(+), 45 deletions(-) diff --git a/versionmgr/changes.go b/versionmgr/changes.go index 8198574e..7deaef3f 100644 --- a/versionmgr/changes.go +++ b/versionmgr/changes.go @@ -42,33 +42,33 @@ func NewChanges(changes []Change) *Changes { return &Changes{changes: changes, idx: -1} } -func (c Changes) Num() int { +func (c *Changes) Num() int { return len(c.changes) } -func (c Changes) Changes() []Change { +func (c *Changes) Changes() []Change { return c.changes } -func (c Changes) Next() (Change, error) { +func (c *Changes) Next() (*Change, error) { if c.idx == len(c.changes)-1 { c.idx++ - return c.changes[c.idx], nil + return &c.changes[c.idx], nil } - return Change{}, io.EOF + return nil, io.EOF } -func (c Changes) Has() bool { +func (c *Changes) Has() bool { return c.idx == len(c.changes)-1 } -func (c Changes) Back() { +func (c *Changes) Back() { if c.idx > -1 { c.idx-- } } -func (c Changes) Reset() { +func (c *Changes) Reset() { c.idx = -1 } @@ -92,15 +92,15 @@ func NewChangesMergeIter(baseChanges *Changes, mergerChanges *Changes, resolver return &ChangesMergeIter{baseChanges: baseChanges, mergerChanges: mergerChanges, resolver: resolver} } -func (cw ChangesMergeIter) Has() bool { +func (cw *ChangesMergeIter) Has() bool { return cw.baseChanges.Has() || cw.mergerChanges.Has() } -func (cw ChangesMergeIter) Reset() { +func (cw *ChangesMergeIter) Reset() { cw.baseChanges.Reset() cw.mergerChanges.Reset() } -func (cw ChangesMergeIter) Next() (*Change, error) { +func (cw *ChangesMergeIter) Next() (*Change, error) { baseNode, baseErr := cw.baseChanges.Next() if baseErr != nil && baseErr != io.EOF { return nil, baseErr @@ -116,46 +116,27 @@ func (cw ChangesMergeIter) Next() (*Change, error) { } if baseErr == io.EOF { - return &mergeNode, nil + return mergeNode, nil } if mergerError == io.EOF { - return &baseNode, nil + return baseNode, nil } compare := strings.Compare(baseNode.Path(), mergeNode.Path()) if compare < 0 { //only merger change cw.baseChanges.Back() - return &mergeNode, nil + return mergeNode, nil } else if compare == 0 { - - //both change - if baseNode.From == nil && mergeNode.From == nil { - //both delete - return &baseNode, nil - } - if bytes.Equal(baseNode.From.Hash(), mergeNode.From.Hash()) { - //both modify/add apply any - return &baseNode, nil - } - //conflict - if cw.resolver != nil { - resolveResult, err := cw.resolver(&baseNode, &mergeNode) - if err != nil { - return nil, err - } - return resolveResult, nil - } - return nil, fmt.Errorf("path %s confilict %w", mergeNode.Path(), ErrConflict) - } else { - //only base change - cw.mergerChanges.Back() - return &baseNode, nil + return cw.compareBothChange(baseNode, mergeNode) } + //only base change + cw.mergerChanges.Back() + return baseNode, nil } -func (cw ChangesMergeIter) compareBothChange(base, merge *Change) (*Change, error) { +func (cw *ChangesMergeIter) compareBothChange(base, merge *Change) (*Change, error) { baseAction, err := base.Action() if err != nil { return nil, err @@ -168,14 +149,14 @@ func (cw ChangesMergeIter) compareBothChange(base, merge *Change) (*Change, erro case merkletrie.Insert: switch mergeAction { case merkletrie.Delete: - return cw.resolver(base, merge) + return cw.resolveConflict(base, merge) case merkletrie.Modify: return nil, fmt.Errorf("%s merge should never be Modify while the other diff is Insert, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues", base.Path()) case merkletrie.Insert: if bytes.Equal(base.From.Hash(), merge.From.Hash()) { return base, nil } - return cw.resolver(base, merge) + return cw.resolveConflict(base, merge) } case merkletrie.Delete: switch mergeAction { @@ -184,25 +165,25 @@ func (cw ChangesMergeIter) compareBothChange(base, merge *Change) (*Change, erro case merkletrie.Insert: return nil, fmt.Errorf("%s merge should never be Insert while the other diff is Delete, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues", base.Path()) case merkletrie.Modify: - return cw.resolver(base, merge) + return cw.resolveConflict(base, merge) } case merkletrie.Modify: switch mergeAction { case merkletrie.Insert: return nil, fmt.Errorf("%s merge should never be Insert while the other diff is Modify, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues", base.Path()) case merkletrie.Delete: - return cw.resolver(base, merge) + return cw.resolveConflict(base, merge) case merkletrie.Modify: if bytes.Equal(base.From.Hash(), merge.From.Hash()) { return base, nil } - return cw.resolver(base, merge) + return cw.resolveConflict(base, merge) } } return nil, fmt.Errorf("not match action") } -func (cw ChangesMergeIter) resolveConflict(base, merge *Change) (*Change, error) { +func (cw *ChangesMergeIter) resolveConflict(base, merge *Change) (*Change, error) { if cw.resolver != nil { resolveResult, err := cw.resolver(base, merge) if err != nil { diff --git a/versionmgr/commit_test.go b/versionmgr/commit_test.go index ad769237..13633439 100644 --- a/versionmgr/commit_test.go +++ b/versionmgr/commit_test.go @@ -65,7 +65,7 @@ b/e.txt |e2 changes, err := baseCommit.DiffCommit(ctx, mergeCommit.Commit().Hash) require.NoError(t, err) - require.Len(t, changes.Num(), 3) + require.Equal(t, 3, changes.Num()) } func makeUser(ctx context.Context, userRepo models.IUserRepo, name string) (*models.User, error) { @@ -95,6 +95,7 @@ func makeRepository(ctx context.Context, repoRepo models.IRepositoryRepo, name s return repoRepo.Insert(ctx, user) } +// nolint func makeCommit(ctx context.Context, commitRepo models.IObjectRepo, treeHash hash.Hash, msg string, parentsHash ...hash.Hash) (*models.Commit, error) { commit := &models.Commit{ Hash: hash.Hash("mock"), From 0af169b5c9c1b93141b21299ab60a0575c7481b6 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Mon, 11 Dec 2023 11:48:33 +0800 Subject: [PATCH 058/210] fix: add test for changes and merge --- versionmgr/changes.go | 103 ++++++++++--- versionmgr/changes_test.go | 281 +++++++++++++++++++++++++++++++++++ versionmgr/commit.go | 44 ++---- versionmgr/commit_node.go | 8 +- versionmgr/commit_test.go | 295 ++++++++++++++++++++++++++++++++++--- versionmgr/worktree.go | 12 +- 6 files changed, 656 insertions(+), 87 deletions(-) create mode 100644 versionmgr/changes_test.go diff --git a/versionmgr/changes.go b/versionmgr/changes.go index 7deaef3f..e46452a5 100644 --- a/versionmgr/changes.go +++ b/versionmgr/changes.go @@ -2,19 +2,45 @@ package versionmgr import ( "bytes" + "errors" "fmt" "io" "sort" "strings" + "github.com/go-git/go-git/v5/utils/merkletrie/noder" + "github.com/go-git/go-git/v5/utils/merkletrie" ) +var ( + ErrActionNotMatch = errors.New("change action not match") + ErrConflict = errors.New("conflict dected but not found resolver") +) + +type IChange interface { + Action() (merkletrie.Action, error) + From() noder.Path + To() noder.Path + Path() string + String() string +} + +var _ IChange = (*Change)(nil) + type Change struct { merkletrie.Change } -func (c Change) Path() string { +func (c *Change) From() noder.Path { + return c.Change.From +} + +func (c *Change) To() noder.Path { + return c.Change.To +} + +func (c *Change) Path() string { action, err := c.Action() if err != nil { panic(err) @@ -22,44 +48,48 @@ func (c Change) Path() string { var path string if action == merkletrie.Delete { - path = c.From.String() + path = c.Change.From.String() } else { - path = c.To.String() + path = c.Change.To.String() } return path } type Changes struct { - changes []Change + changes []IChange idx int } -func NewChanges(changes []Change) *Changes { +func NewChanges(changes []IChange) *Changes { sort.Slice(changes, func(i, j int) bool { - return strings.Compare(changes[i].Path(), changes[j].Path()) > 0 //i > j + return strings.Compare(changes[i].Path(), changes[j].Path()) < 0 }) + return &Changes{changes: changes, idx: -1} } func (c *Changes) Num() int { return len(c.changes) } +func (c *Changes) Index(idx int) IChange { + return c.changes[idx] +} -func (c *Changes) Changes() []Change { +func (c *Changes) Changes() []IChange { return c.changes } -func (c *Changes) Next() (*Change, error) { - if c.idx == len(c.changes)-1 { +func (c *Changes) Next() (IChange, error) { + if c.idx < len(c.changes)-1 { c.idx++ - return &c.changes[c.idx], nil + return c.changes[c.idx], nil } return nil, io.EOF } func (c *Changes) Has() bool { - return c.idx == len(c.changes)-1 + return c.idx < len(c.changes)-1 } func (c *Changes) Back() { @@ -73,14 +103,38 @@ func (c *Changes) Reset() { } func newChanges(mChanges merkletrie.Changes) *Changes { - changes := make([]Change, len(mChanges)) + changes := make([]IChange, len(mChanges)) for index, change := range mChanges { - changes[index] = Change{change} + changes[index] = &Change{change} } return NewChanges(changes) } -type ConflictResolver func(base *Change, merged *Change) (*Change, error) +type ConflictResolver func(base IChange, merged IChange) (IChange, error) + +func LeastHashResolve(base IChange, merged IChange) (IChange, error) { + baseAction, err := base.Action() + if err != nil { + return nil, err + } + + mergeAction, err := merged.Action() + if err != nil { + return nil, err + } + + if baseAction == merkletrie.Delete { + return merged, nil + } + if mergeAction == merkletrie.Delete { + return base, nil + } + + if bytes.Compare(base.To().Hash(), merged.To().Hash()) < 0 { + return base, nil + } + return merged, nil +} type ChangesMergeIter struct { baseChanges *Changes @@ -100,7 +154,7 @@ func (cw *ChangesMergeIter) Reset() { cw.baseChanges.Reset() cw.mergerChanges.Reset() } -func (cw *ChangesMergeIter) Next() (*Change, error) { +func (cw *ChangesMergeIter) Next() (IChange, error) { baseNode, baseErr := cw.baseChanges.Next() if baseErr != nil && baseErr != io.EOF { return nil, baseErr @@ -124,7 +178,7 @@ func (cw *ChangesMergeIter) Next() (*Change, error) { } compare := strings.Compare(baseNode.Path(), mergeNode.Path()) - if compare < 0 { + if compare > 0 { //only merger change cw.baseChanges.Back() return mergeNode, nil @@ -136,7 +190,7 @@ func (cw *ChangesMergeIter) Next() (*Change, error) { return baseNode, nil } -func (cw *ChangesMergeIter) compareBothChange(base, merge *Change) (*Change, error) { +func (cw *ChangesMergeIter) compareBothChange(base, merge IChange) (IChange, error) { baseAction, err := base.Action() if err != nil { return nil, err @@ -151,9 +205,9 @@ func (cw *ChangesMergeIter) compareBothChange(base, merge *Change) (*Change, err case merkletrie.Delete: return cw.resolveConflict(base, merge) case merkletrie.Modify: - return nil, fmt.Errorf("%s merge should never be Modify while the other diff is Insert, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues", base.Path()) + return nil, fmt.Errorf("%s merge should never be Modify while the other diff is Insert, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues %w", base.Path(), ErrActionNotMatch) case merkletrie.Insert: - if bytes.Equal(base.From.Hash(), merge.From.Hash()) { + if bytes.Equal(base.To().Hash(), merge.To().Hash()) { return base, nil } return cw.resolveConflict(base, merge) @@ -163,27 +217,28 @@ func (cw *ChangesMergeIter) compareBothChange(base, merge *Change) (*Change, err case merkletrie.Delete: return base, nil case merkletrie.Insert: - return nil, fmt.Errorf("%s merge should never be Insert while the other diff is Delete, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues", base.Path()) + return nil, fmt.Errorf("%s merge should never be Insert while the other diff is Delete, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues %w", base.Path(), ErrActionNotMatch) case merkletrie.Modify: return cw.resolveConflict(base, merge) } case merkletrie.Modify: switch mergeAction { case merkletrie.Insert: - return nil, fmt.Errorf("%s merge should never be Insert while the other diff is Modify, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues", base.Path()) + return nil, fmt.Errorf("%s merge should never be Insert while the other diff is Modify, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues %w", base.Path(), ErrActionNotMatch) case merkletrie.Delete: return cw.resolveConflict(base, merge) case merkletrie.Modify: - if bytes.Equal(base.From.Hash(), merge.From.Hash()) { + if bytes.Equal(base.To().Hash(), merge.To().Hash()) { return base, nil } return cw.resolveConflict(base, merge) } } - return nil, fmt.Errorf("not match action") + //should never come here + return nil, ErrActionNotMatch } -func (cw *ChangesMergeIter) resolveConflict(base, merge *Change) (*Change, error) { +func (cw *ChangesMergeIter) resolveConflict(base, merge IChange) (IChange, error) { if cw.resolver != nil { resolveResult, err := cw.resolver(base, merge) if err != nil { diff --git a/versionmgr/changes_test.go b/versionmgr/changes_test.go new file mode 100644 index 00000000..5c29bcf8 --- /dev/null +++ b/versionmgr/changes_test.go @@ -0,0 +1,281 @@ +package versionmgr + +import ( + "path/filepath" + "strconv" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/go-git/go-git/v5/utils/merkletrie" + + "github.com/jiaozifs/jiaozifs/utils/hash" + + "github.com/go-git/go-git/v5/utils/merkletrie/noder" +) + +var _ noder.Noder = (*MockNode)(nil) + +type MockNode struct { + name string + hash hash.Hash +} + +func NewMockNode(name string, hash hash.Hash) *MockNode { + return &MockNode{name: name, hash: hash} +} + +func (m MockNode) Hash() []byte { + return m.hash +} + +func (m MockNode) String() string { + //TODO implement me + panic("implement me") +} + +func (m MockNode) Name() string { + return m.name +} + +func (m MockNode) IsDir() bool { + //TODO implement me + panic("implement me") +} + +func (m MockNode) Children() ([]noder.Noder, error) { + //TODO implement me + panic("implement me") +} + +func (m MockNode) NumChildren() (int, error) { + //TODO implement me + panic("implement me") +} + +func (m MockNode) Skip() bool { + //TODO implement me + panic("implement me") +} + +var _ IChange = (*mockChange)(nil) + +type mockChange struct { + action merkletrie.Action + path string + from noder.Path + to noder.Path +} + +func (m mockChange) Action() (merkletrie.Action, error) { + return m.action, nil +} + +func (m mockChange) From() noder.Path { + return m.from +} + +func (m mockChange) To() noder.Path { + return m.to +} + +func (m mockChange) Path() string { + return m.path +} + +func (m mockChange) String() string { + //TODO implement me + panic("implement me") +} + +func makeMockChange(testData string) (*Changes, error) { + var changes []IChange + for _, line := range strings.Split(testData, "\n") { + line = strings.TrimSpace(line) + if len(line) == 0 { + continue + } + segs := strings.Split(line, "|") + num, err := strconv.Atoi(segs[0]) + if err != nil { + return nil, err + } + action := merkletrie.Action(num) + pHash := hash.Hash(strings.Trim(segs[2], " \t/")) + fullPath := filepath.Clean(strings.Trim(segs[1], " \t/")) + pathSeg := strings.Split(fullPath, "/") + path := make([]noder.Noder, len(pathSeg)) + for index, p := range pathSeg { + path[index] = NewMockNode(p, pHash) + } + c := &mockChange{ + action: action, + path: fullPath, + } + switch action { + case merkletrie.Delete: + c.from = path + c.to = nil + case merkletrie.Insert: + c.from = nil + c.to = path + case merkletrie.Modify: + c.from = nil + c.to = path + } + + changes = append(changes, c) + } + + return NewChanges(changes), nil +} + +func TestNewChangesMergeIter(t *testing.T) { + t.Run("simple just add", func(t *testing.T) { + changeData1 := ` +1|a.txt|h1 +1|b/a.txt|h2 +` + changeSet1, err := makeMockChange(changeData1) + require.NoError(t, err) + changeData2 := ` +1|c.txt|h1 +1|d/a.txt|h2 +` + changeSet2, err := makeMockChange(changeData2) + require.NoError(t, err) + iter := NewChangesMergeIter(changeSet1, changeSet2, nil) + + var finalChjange []IChange + for iter.Has() { + change, err := iter.Next() + require.NoError(t, err) + finalChjange = append(finalChjange, change) + } + require.Len(t, finalChjange, 4) + require.Equal(t, "a.txt", finalChjange[0].Path()) + require.Equal(t, "b/a.txt", finalChjange[1].Path()) + require.Equal(t, "c.txt", finalChjange[2].Path()) + require.Equal(t, "d/a.txt", finalChjange[3].Path()) + }) + + t.Run("keep same hash", func(t *testing.T) { + changeData1 := ` +1|a.txt|h1 +2|b/d.txt|h2 +3|b/a.txt|h2 +` + changeSet1, err := makeMockChange(changeData1) + require.NoError(t, err) + changeData2 := ` +1|a.txt|h1 +2|b/d.txt|h2 +3|b/a.txt|h2 +` + changeSet2, err := makeMockChange(changeData2) + require.NoError(t, err) + iter := NewChangesMergeIter(changeSet1, changeSet2, nil) + + var finalChjange []IChange + for iter.Has() { + change, err := iter.Next() + require.NoError(t, err) + finalChjange = append(finalChjange, change) + } + require.Len(t, finalChjange, 3) + require.Equal(t, "a.txt", finalChjange[0].Path()) + require.Equal(t, "b/a.txt", finalChjange[1].Path()) + require.Equal(t, "b/d.txt", finalChjange[2].Path()) + }) + + t.Run("error while add conflict", func(t *testing.T) { + changeData1 := ` +1|a.txt|h1 +` + changeSet1, err := makeMockChange(changeData1) + require.NoError(t, err) + changeData2 := ` +1|a.txt|h2 +` + changeSet2, err := makeMockChange(changeData2) + require.NoError(t, err) + iter := NewChangesMergeIter(changeSet1, changeSet2, nil) + + for iter.Has() { + _, err = iter.Next() + require.ErrorIs(t, err, ErrConflict) + } + }) + + t.Run("error while modify conflict", func(t *testing.T) { + changeData1 := ` +3|a.txt|h1 +` + changeSet1, err := makeMockChange(changeData1) + require.NoError(t, err) + changeData2 := ` +3|a.txt|h2 +` + changeSet2, err := makeMockChange(changeData2) + require.NoError(t, err) + iter := NewChangesMergeIter(changeSet1, changeSet2, nil) + + for iter.Has() { + _, err = iter.Next() + require.ErrorIs(t, err, ErrConflict) + } + }) + + t.Run("not conflict while both delete", func(t *testing.T) { + changeData1 := ` +2|a.txt|h1 +` + changeSet1, err := makeMockChange(changeData1) + require.NoError(t, err) + changeData2 := ` +2|a.txt|h2 +` + changeSet2, err := makeMockChange(changeData2) + require.NoError(t, err) + iter := NewChangesMergeIter(changeSet1, changeSet2, nil) + + var finalChjange []IChange + for iter.Has() { + change, err := iter.Next() + require.NoError(t, err) + finalChjange = append(finalChjange, change) + } + require.Len(t, finalChjange, 1) + require.Equal(t, "a.txt", finalChjange[0].Path()) + }) + + t.Run("resovler select conflict", func(t *testing.T) { + changeData1 := ` +1|a.txt|h1 +2|b.txt|h3 +` + changeSet1, err := makeMockChange(changeData1) + require.NoError(t, err) + changeData2 := ` +1|a.txt|h2 +2|b.txt|h4 +` + changeSet2, err := makeMockChange(changeData2) + require.NoError(t, err) + iter := NewChangesMergeIter(changeSet1, changeSet2, LeastHashResolve) + + var finalChjange []IChange + for iter.Has() { + change, err := iter.Next() + require.NoError(t, err) + finalChjange = append(finalChjange, change) + } + require.Len(t, finalChjange, 2) + require.Equal(t, "a.txt", finalChjange[0].Path()) + require.Equal(t, "h1", string(finalChjange[0].To().Hash())) + + require.Equal(t, "b.txt", finalChjange[1].Path()) + require.Equal(t, "h3", string(finalChjange[1].From().Hash())) + }) +} diff --git a/versionmgr/commit.go b/versionmgr/commit.go index 2d87d160..31fe5d74 100644 --- a/versionmgr/commit.go +++ b/versionmgr/commit.go @@ -3,7 +3,6 @@ package versionmgr import ( "bytes" "context" - "errors" "fmt" "time" @@ -17,8 +16,7 @@ import ( ) var ( - commitLog = logging.Logger("commit") - ErrConflict = errors.New("conflict dected but not found resolver") + commitLog = logging.Logger("commit") ) type CommitOp struct { @@ -42,7 +40,7 @@ func (commitOp *CommitOp) Commit() *models.Commit { return commitOp.commit } -func (commitOp *CommitOp) AddCommit(ctx context.Context, committerID, wipID uuid.UUID, msg string) (*CommitOp, error) { +func (commitOp *CommitOp) AddCommit(ctx context.Context, committer *models.User, wipID uuid.UUID, msg string) (*CommitOp, error) { wip, err := commitOp.wip.Get(ctx, &models.GetWipParam{ ID: wipID, }) @@ -50,13 +48,6 @@ func (commitOp *CommitOp) AddCommit(ctx context.Context, committerID, wipID uuid return nil, err } - committer, err := commitOp.user.Get(ctx, &models.GetUserParam{ - ID: committerID, - }) - if err != nil { - return nil, err - } - creator, err := commitOp.user.Get(ctx, &models.GetUserParam{ ID: wip.CreateID, }) @@ -138,13 +129,7 @@ func (commitOp *CommitOp) DiffCommit(ctx context.Context, toCommitID hash.Hash) return newChanges(changes), nil } -func (commitOp *CommitOp) Merge(ctx context.Context, mergerID uuid.UUID, toMergeCommitHash hash.Hash, msg string) (*models.Commit, error) { - merger, err := commitOp.user.Get(ctx, &models.GetUserParam{ - ID: mergerID, - }) - if err != nil { - return nil, err - } +func (commitOp *CommitOp) Merge(ctx context.Context, merger *models.User, toMergeCommitHash hash.Hash, msg string, resolver ConflictResolver) (*models.Commit, error) { toMergeCommit, err := commitOp.object.Commit(ctx, toMergeCommitHash) if err != nil { @@ -181,7 +166,7 @@ func (commitOp *CommitOp) Merge(ctx context.Context, mergerID uuid.UUID, toMerge } } - // three way merge + // three-way merge bestAncestor, err := baseCommitNode.MergeBase(toMergeCommitNode) if err != nil { return nil, err @@ -200,7 +185,7 @@ func (commitOp *CommitOp) Merge(ctx context.Context, mergerID uuid.UUID, toMerge object: commitOp.object, wip: commitOp.wip, } - virtualCommit, err := firstCommit.Merge(ctx, mergerID, bestAncestor[1].Commit().Hash, "") + virtualCommit, err := firstCommit.Merge(ctx, merger, bestAncestor[1].Commit().Hash, "", resolver) if err != nil { return nil, err } @@ -208,29 +193,30 @@ func (commitOp *CommitOp) Merge(ctx context.Context, mergerID uuid.UUID, toMerge bestCommit = NewCommitNode(ctx, virtualCommit, commitOp.object) } - baseDiff, err := commitOp.DiffCommit(ctx, bestCommit.Commit().Hash) - if err != nil { - return nil, err - } - - mergeCommitOp := &CommitOp{ - commit: toMergeCommit, + bestCommitOp := &CommitOp{ + commit: bestAncestor[0].Commit(), user: commitOp.user, object: commitOp.object, wip: commitOp.wip, } - mergeDiff, err := mergeCommitOp.DiffCommit(ctx, bestCommit.Commit().Hash) + + baseDiff, err := bestCommitOp.DiffCommit(ctx, commitOp.Commit().Hash) + if err != nil { + return nil, err + } + + mergeDiff, err := bestCommitOp.DiffCommit(ctx, toMergeCommit.Hash) if err != nil { return nil, err } //merge diff - cmw := NewChangesMergeIter(baseDiff, mergeDiff, nil) workTree, err := NewWorkTree(ctx, commitOp.object, models.NewRootTreeEntry(bestCommit.Commit().TreeHash)) if err != nil { return nil, err } + cmw := NewChangesMergeIter(baseDiff, mergeDiff, resolver) for cmw.Has() { change, err := cmw.Next() if err != nil { diff --git a/versionmgr/commit_node.go b/versionmgr/commit_node.go index a3236c3f..d48377b1 100644 --- a/versionmgr/commit_node.go +++ b/versionmgr/commit_node.go @@ -117,15 +117,15 @@ func newArrayCommitIter(commits []*CommitNode) *arraryCommitIter { } } -func (a arraryCommitIter) Next() (*CommitNode, error) { - if a.idx == len(a.commits)-1 { +func (a *arraryCommitIter) Next() (*CommitNode, error) { + if a.idx < len(a.commits)-1 { a.idx++ return a.commits[a.idx], nil } return nil, io.EOF } -func (a arraryCommitIter) ForEach(f func(*CommitNode) error) error { +func (a *arraryCommitIter) ForEach(f func(*CommitNode) error) error { for _, commit := range a.commits { err := f(commit) if errors.Is(err, storer.ErrStop) { @@ -138,6 +138,6 @@ func (a arraryCommitIter) ForEach(f func(*CommitNode) error) error { return nil } -func (a arraryCommitIter) Has() bool { +func (a *arraryCommitIter) Has() bool { return a.idx == len(a.commits)-1 } diff --git a/versionmgr/commit_test.go b/versionmgr/commit_test.go index 13633439..591f9218 100644 --- a/versionmgr/commit_test.go +++ b/versionmgr/commit_test.go @@ -6,6 +6,8 @@ import ( "testing" "time" + "github.com/go-git/go-git/v5/utils/merkletrie" + "github.com/google/uuid" "github.com/stretchr/testify/require" @@ -17,28 +19,222 @@ import ( "github.com/jiaozifs/jiaozifs/testhelper" ) -func TestCommitOp_DiffCommit(t *testing.T) { +func TestCommitOpDiffCommit(t *testing.T) { ctx := context.Background() postgres, _, db := testhelper.SetupDatabase(ctx, t) defer postgres.Stop() //nolint repo := models.NewRepo(db) + + user, err := makeUser(ctx, repo.UserRepo(), "admin") + require.NoError(t, err) + + project, err := makeRepository(ctx, repo.RepositoryRepo(), "testproject") + require.NoError(t, err) + + //base branch + baseRef, err := makeRef(ctx, repo.RefRepo(), "feat/base", project.ID, hash.Hash("a")) + require.NoError(t, err) + //commit1 a.txt b/c.txt b/e.txt //commit2 a.txt b/d.txt b/e.txt testData1 := ` -a.txt |a -b/c.txt |c -b/e.txt |e1 +1|a.txt |a +1|b/c.txt |c +1|b/e.txt |e1 ` + + root1, err := makeRoot(ctx, repo.ObjectRepo(), EmptyDirEntry, testData1) + require.NoError(t, err) + baseWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseRef.ID, EmptyRoot.Hash, root1.Hash) + require.NoError(t, err) + + baseCommit, err := NewCommitOp(repo, nil).AddCommit(ctx, user, baseWip.ID, "base commit") + require.NoError(t, err) + testData2 := ` -a.txt |a -b/d.txt |d -b/e.txt |e2 +3|a.txt |a1 +2|b/c.txt |d +3|b/e.txt |e2 +1|b/g.txt |g1 +` + root2, err := makeRoot(ctx, repo.ObjectRepo(), models.NewRootTreeEntry(root1.Hash), testData2) + require.NoError(t, err) + + secondWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseRef.ID, EmptyRoot.Hash, root2.Hash) + require.NoError(t, err) + secondCommit, err := NewCommitOp(repo, nil).AddCommit(ctx, user, secondWip.ID, "merge commit") + require.NoError(t, err) + + changes, err := baseCommit.DiffCommit(ctx, secondCommit.Commit().Hash) + require.NoError(t, err) + require.Equal(t, 4, changes.Num()) + require.Equal(t, "a.txt", changes.Index(0).Path()) + action, err := changes.Index(0).Action() + require.NoError(t, err) + require.Equal(t, merkletrie.Modify, action) + + require.Equal(t, "b/c.txt", changes.Index(1).Path()) + action, err = changes.Index(1).Action() + require.NoError(t, err) + require.Equal(t, merkletrie.Delete, action) + require.Equal(t, "b/e.txt", changes.Index(2).Path()) + action, err = changes.Index(2).Action() + require.NoError(t, err) + require.Equal(t, merkletrie.Modify, action) + + require.Equal(t, "b/g.txt", changes.Index(3).Path()) + action, err = changes.Index(3).Action() + require.NoError(t, err) + require.Equal(t, merkletrie.Insert, action) +} + +// TestCommitOpMerge +//example +// A -----C +// | | \ +// | | \ +// / \ F \ +// / \ / \ +// root AB CG +// \ / \ / +// \ / \ / +// B-D-E--- G + +func TestCommitOpMerge(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + repo := models.NewRepo(db) + + user, err := makeUser(ctx, repo.UserRepo(), "admin") + require.NoError(t, err) + + project, err := makeRepository(ctx, repo.RepositoryRepo(), "testproject") + require.NoError(t, err) + + testData := ` +1|a.txt |h1 +1|b/c.txt |h2 +` + oriRoot, err := makeRoot(ctx, repo.ObjectRepo(), EmptyDirEntry, testData) + require.NoError(t, err) + //base branch + baseRef, err := makeRef(ctx, repo.RefRepo(), "feat/base", project.ID, hash.Hash("a")) + require.NoError(t, err) + + oriWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseRef.ID, hash.Hash{}, oriRoot.Hash) + require.NoError(t, err) + + oriCommit, err := NewCommitOp(repo, nil).AddCommit(ctx, user, oriWip.ID, "") + require.NoError(t, err) + + //modify a.txt + //CommitA + testData = ` +3|a.txt |h5 +3|b/c.txt |h2 +` + baseModify, err := makeRoot(ctx, repo.ObjectRepo(), models.NewRootTreeEntry(oriRoot.Hash), testData) + require.NoError(t, err) + + baseWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseRef.ID, oriRoot.Hash, baseModify.Hash) + require.NoError(t, err) + + commitA, err := NewCommitOp(repo, oriCommit.Commit()).AddCommit(ctx, user, baseWip.ID, "commit a") + require.NoError(t, err) + + //toMerge branch + mergeRef, err := makeRef(ctx, repo.RefRepo(), "feat/merge", project.ID, hash.Hash("a")) + require.NoError(t, err) + + //modify a.txt + //CommitB + testData = ` +3|a.txt |h4 +3|b/c.txt |h2 +` + mergeModify, err := makeRoot(ctx, repo.ObjectRepo(), models.NewRootTreeEntry(oriRoot.Hash), testData) + require.NoError(t, err) + mergeWip, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeRef.ID, oriRoot.Hash, mergeModify.Hash) + require.NoError(t, err) + commitB, err := NewCommitOp(repo, oriCommit.Commit()).AddCommit(ctx, user, mergeWip.ID, "commit b") + require.NoError(t, err) + + //CommitAB + commitAB, err := commitA.Merge(ctx, user, commitB.Commit().Hash, "commit ab", LeastHashResolve) + require.NoError(t, err) + + //CommitAS + testData = ` +1|x.txt |h4 +` + rootF, err := makeRoot(ctx, repo.ObjectRepo(), models.NewRootTreeEntry(commitAB.TreeHash), testData) + require.NoError(t, err) + mergeWipF, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeRef.ID, commitAB.TreeHash, rootF.Hash) + require.NoError(t, err) + commitF, err := NewCommitOp(repo, oriCommit.Commit()).AddCommit(ctx, user, mergeWipF.ID, "commit f") + require.NoError(t, err) + + //commitC + commitC, err := commitA.Merge(ctx, user, commitF.Commit().Hash, "commit c", LeastHashResolve) + require.NoError(t, err) + + //commitD + testData = ` +3|a.txt |h5 +3|b/c.txt |h6 +1|g/c.txt |h7 ` - root1, err := makeRoot(ctx, repo.ObjectRepo(), testData1) + modifyD, err := makeRoot(ctx, repo.ObjectRepo(), models.NewRootTreeEntry(commitB.Commit().TreeHash), testData) + require.NoError(t, err) + mergeWipD, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeRef.ID, commitB.Commit().Hash, modifyD.Hash) + require.NoError(t, err) + commitD, err := commitB.AddCommit(ctx, user, mergeWipD.ID, "commit d") + require.NoError(t, err) + //commitE + testData = ` +2|a.txt |h4 +` + modifyE, err := makeRoot(ctx, repo.ObjectRepo(), models.NewRootTreeEntry(commitD.Commit().TreeHash), testData) + require.NoError(t, err) + mergeWipE, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeRef.ID, commitD.Commit().Hash, modifyE.Hash) + require.NoError(t, err) + commitE, err := commitD.AddCommit(ctx, user, mergeWipE.ID, "commit e") + require.NoError(t, err) + + //test fast-ward + + fastMergeCommit, err := commitB.Merge(ctx, user, commitE.Commit().Hash, "", LeastHashResolve) require.NoError(t, err) - root2, err := makeRoot(ctx, repo.ObjectRepo(), testData2) + require.Equal(t, commitE.Commit().Hash.Hex(), fastMergeCommit.Hash.Hex()) + + //commitG + commitG, err := commitE.Merge(ctx, user, commitAB.Hash, "commit c", LeastHashResolve) + require.NoError(t, err) + + _, err = NewCommitOp(repo, commitC).Merge(ctx, user, commitG.Hash, "commit c", LeastHashResolve) require.NoError(t, err) +} + +// TestCrissCrossMerge +// +// example +// +// C--------D +// / \ / \ +// / \ / \ +// root * CG +// \ / \ / +// \ / \ / +// B-------G +func TestCrissCrossMerge(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + repo := models.NewRepo(db) user, err := makeUser(ctx, repo.UserRepo(), "admin") require.NoError(t, err) @@ -46,26 +242,61 @@ b/e.txt |e2 project, err := makeRepository(ctx, repo.RepositoryRepo(), "testproject") require.NoError(t, err) + testData := ` +1|a.txt |h1 +1|b.txt |h2 +` + oriRoot, err := makeRoot(ctx, repo.ObjectRepo(), EmptyDirEntry, testData) + require.NoError(t, err) //base branch baseRef, err := makeRef(ctx, repo.RefRepo(), "feat/base", project.ID, hash.Hash("a")) require.NoError(t, err) - baseWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseRef.ID, root1.Hash) + + oriWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseRef.ID, hash.Hash{}, oriRoot.Hash) + require.NoError(t, err) + + oriCommit, err := NewCommitOp(repo, nil).AddCommit(ctx, user, oriWip.ID, "") + require.NoError(t, err) + + //CommitA + testData = ` +3|a.txt |h1 +3|b.txt |h3 +` + baseModify, err := makeRoot(ctx, repo.ObjectRepo(), models.NewRootTreeEntry(oriRoot.Hash), testData) + require.NoError(t, err) + + baseWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseRef.ID, oriRoot.Hash, baseModify.Hash) require.NoError(t, err) - baseCommit, err := NewCommitOp(repo, nil).AddCommit(ctx, user.ID, baseWip.ID, "base commit") + commitA, err := NewCommitOp(repo, oriCommit.Commit()).AddCommit(ctx, user, baseWip.ID, "commit a") require.NoError(t, err) //toMerge branch mergeRef, err := makeRef(ctx, repo.RefRepo(), "feat/merge", project.ID, hash.Hash("a")) require.NoError(t, err) - mergeWip, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeRef.ID, root2.Hash) + + //modify a.txt + //CommitB + testData = ` +3|a.txt |h4 +3|b.txt |h2 +` + mergeModify, err := makeRoot(ctx, repo.ObjectRepo(), models.NewRootTreeEntry(oriRoot.Hash), testData) + require.NoError(t, err) + mergeWip, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeRef.ID, oriRoot.Hash, mergeModify.Hash) require.NoError(t, err) - mergeCommit, err := NewCommitOp(repo, nil).AddCommit(ctx, user.ID, mergeWip.ID, "merge commit") + commitB, err := NewCommitOp(repo, oriCommit.Commit()).AddCommit(ctx, user, mergeWip.ID, "commit b") require.NoError(t, err) - changes, err := baseCommit.DiffCommit(ctx, mergeCommit.Commit().Hash) + commitAB, err := commitA.Merge(ctx, user, commitB.Commit().Hash, "commit ab", LeastHashResolve) + require.NoError(t, err) + + commitBA, err := commitB.Merge(ctx, user, commitA.Commit().Hash, "commit ba", LeastHashResolve) + require.NoError(t, err) + + _, err = NewCommitOp(repo, commitAB).Merge(ctx, user, commitBA.Hash, "cross commit", LeastHashResolve) require.NoError(t, err) - require.Equal(t, 3, changes.Num()) } func makeUser(ctx context.Context, userRepo models.IUserRepo, name string) (*models.User, error) { @@ -120,6 +351,7 @@ func makeCommit(ctx context.Context, commitRepo models.IObjectRepo, treeHash has } return obj.Commit(), nil } + func makeRef(ctx context.Context, refRepo models.IRefRepo, name string, repoID uuid.UUID, commitHash hash.Hash) (*models.Ref, error) { ref := &models.Ref{ RepositoryID: repoID, @@ -133,10 +365,10 @@ func makeRef(ctx context.Context, refRepo models.IRefRepo, name string, repoID u return refRepo.Insert(ctx, ref) } -func makeWip(ctx context.Context, wipRepo models.IWipRepo, repoID, refID uuid.UUID, curHash hash.Hash) (*models.WorkingInProcess, error) { +func makeWip(ctx context.Context, wipRepo models.IWipRepo, repoID, refID uuid.UUID, parentHash, curHash hash.Hash) (*models.WorkingInProcess, error) { wip := &models.WorkingInProcess{ CurrentTree: curHash, - ParentTree: hash.Hash("mock"), + ParentTree: parentHash, RefID: refID, RepositoryID: repoID, CreateID: uuid.UUID{}, @@ -145,9 +377,10 @@ func makeWip(ctx context.Context, wipRepo models.IWipRepo, repoID, refID uuid.UU } return wipRepo.Insert(ctx, wip) } -func makeRoot(ctx context.Context, objRepo models.IObjectRepo, testData string) (*models.TreeNode, error) { + +func makeRoot(ctx context.Context, objRepo models.IObjectRepo, treeEntry models.TreeEntry, testData string) (*models.TreeNode, error) { lines := strings.Split(testData, "\n") - treeOp, err := NewWorkTree(ctx, objRepo, EmptyDirEntry) + treeOp, err := NewWorkTree(ctx, objRepo, treeEntry) if err != nil { return nil, err } @@ -156,8 +389,8 @@ func makeRoot(ctx context.Context, objRepo models.IObjectRepo, testData string) continue } commitData := strings.Split(strings.TrimSpace(line), "|") - fullPath := strings.TrimSpace(commitData[0]) - fileHash := strings.TrimSpace(commitData[1]) + fullPath := strings.TrimSpace(commitData[1]) + fileHash := strings.TrimSpace(commitData[2]) blob := &models.Blob{ Hash: hash.Hash(fileHash), Type: models.BlobObject, @@ -166,10 +399,24 @@ func makeRoot(ctx context.Context, objRepo models.IObjectRepo, testData string) UpdatedAt: time.Now(), } - err = treeOp.AddLeaf(ctx, fullPath, blob) - if err != nil { - return nil, err + if commitData[0] == "1" { + err = treeOp.AddLeaf(ctx, fullPath, blob) + if err != nil { + return nil, err + } + } else if commitData[0] == "3" { + err = treeOp.ReplaceLeaf(ctx, fullPath, blob) + if err != nil { + return nil, err + } + } else { + //2 + err = treeOp.RemoveEntry(ctx, fullPath) + if err != nil { + return nil, err + } } + } return treeOp.Root().TreeNode(), nil } diff --git a/versionmgr/worktree.go b/versionmgr/worktree.go index be1ad8b2..6a7dc201 100644 --- a/versionmgr/worktree.go +++ b/versionmgr/worktree.go @@ -486,26 +486,26 @@ func (workTree *WorkTree) Ls(ctx context.Context, fullPath string) ([]models.Tre return lastNode.Node().SubObjects, nil } -func (workTree *WorkTree) ApplyOneChange(ctx context.Context, change *Change) error { +func (workTree *WorkTree) ApplyOneChange(ctx context.Context, change IChange) error { action, err := change.Action() if err != nil { return err } switch action { case merkletrie.Insert: - blob, err := workTree.object.Blob(ctx, change.From.Hash()) + blob, err := workTree.object.Blob(ctx, change.To().Hash()) if err != nil { return err } - return workTree.AddLeaf(ctx, change.From.String(), blob) + return workTree.AddLeaf(ctx, change.To().String(), blob) case merkletrie.Delete: - return workTree.RemoveEntry(ctx, change.From.String()) + return workTree.RemoveEntry(ctx, change.From().String()) case merkletrie.Modify: - blob, err := workTree.object.Blob(ctx, change.From.Hash()) + blob, err := workTree.object.Blob(ctx, change.To().Hash()) if err != nil { return err } - return workTree.ReplaceLeaf(ctx, change.From.String(), blob) + return workTree.ReplaceLeaf(ctx, change.To().String(), blob) } return fmt.Errorf("unexpect change action: %s", action) } From 95f20afabb5944262d28efb24599e7c0939d9fd0 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Mon, 11 Dec 2023 15:28:28 +0800 Subject: [PATCH 059/210] feat: add comment --- block/gs/adapter.go | 2 +- versionmgr/changes.go | 27 +++++++++++++++++++++++++++ versionmgr/commit.go | 7 +++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/block/gs/adapter.go b/block/gs/adapter.go index bb6f2dde..d19ebecc 100644 --- a/block/gs/adapter.go +++ b/block/gs/adapter.go @@ -220,7 +220,7 @@ func (a *Adapter) Remove(ctx context.Context, obj block.ObjectPointer) error { } err = a.client.Bucket(bucket).Object(key).Delete(ctx) if err != nil { - return fmt.Errorf("object(%q).Delete: %w", key, err) + return fmt.Errorf("Object(%q).Delete: %w", key, err) } return nil } diff --git a/versionmgr/changes.go b/versionmgr/changes.go index e46452a5..f6bcc545 100644 --- a/versionmgr/changes.go +++ b/versionmgr/changes.go @@ -28,18 +28,22 @@ type IChange interface { var _ IChange = (*Change)(nil) +// Change wrap merkletrie changes for test type Change struct { merkletrie.Change } +// From return change from func (c *Change) From() noder.Path { return c.Change.From } +// To return change to func (c *Change) To() noder.Path { return c.Change.To } +// Path return change path func (c *Change) Path() string { action, err := c.Action() if err != nil { @@ -56,11 +60,13 @@ func (c *Change) Path() string { return path } +// Changes used to recored changes between commit, also provider iter function type Changes struct { changes []IChange idx int } +// NewChanges create a change set func NewChanges(changes []IChange) *Changes { sort.Slice(changes, func(i, j int) bool { return strings.Compare(changes[i].Path(), changes[j].Path()) < 0 @@ -69,17 +75,22 @@ func NewChanges(changes []IChange) *Changes { return &Changes{changes: changes, idx: -1} } +// Num return change number func (c *Changes) Num() int { return len(c.changes) } + +// Index get change by array index func (c *Changes) Index(idx int) IChange { return c.changes[idx] } +// Changes return all changes func (c *Changes) Changes() []IChange { return c.changes } +// Next get next element in array func (c *Changes) Next() (IChange, error) { if c.idx < len(c.changes)-1 { c.idx++ @@ -88,16 +99,19 @@ func (c *Changes) Next() (IChange, error) { return nil, io.EOF } +// Has checke whether all element was consumed func (c *Changes) Has() bool { return c.idx < len(c.changes)-1 } +// Back a element in array func (c *Changes) Back() { if c.idx > -1 { c.idx-- } } +// Reset result change iter func (c *Changes) Reset() { c.idx = -1 } @@ -110,8 +124,10 @@ func newChanges(mChanges merkletrie.Changes) *Changes { return NewChanges(changes) } +// ConflictResolver resolve conflict between two change type ConflictResolver func(base IChange, merged IChange) (IChange, error) +// LeastHashResolve use least hash change for test func LeastHashResolve(base IChange, merged IChange) (IChange, error) { baseAction, err := base.Action() if err != nil { @@ -136,24 +152,35 @@ func LeastHashResolve(base IChange, merged IChange) (IChange, error) { return merged, nil } +// ChangesMergeIter walk two changes set and try to resolve type ChangesMergeIter struct { baseChanges *Changes mergerChanges *Changes resolver ConflictResolver } +// NewChangesMergeIter create a changes iter with two changes set and resolver function func NewChangesMergeIter(baseChanges *Changes, mergerChanges *Changes, resolver ConflictResolver) *ChangesMergeIter { return &ChangesMergeIter{baseChanges: baseChanges, mergerChanges: mergerChanges, resolver: resolver} } +// Has check if any changes func (cw *ChangesMergeIter) Has() bool { return cw.baseChanges.Has() || cw.mergerChanges.Has() } +// Reset reset changes func (cw *ChangesMergeIter) Reset() { cw.baseChanges.Reset() cw.mergerChanges.Reset() } + +// Next find change file, first sort each file in change, pop files from two changes, compare filename, +// +// base file < merge file, pop base change and put merge file back to queue +// base file > merge file, pop merge file and put base file back to queue +// both file name match, try to resolve file changes +// if one of the queue consume up, pick left change in the other queue func (cw *ChangesMergeIter) Next() (IChange, error) { baseNode, baseErr := cw.baseChanges.Next() if baseErr != nil && baseErr != io.EOF { diff --git a/versionmgr/commit.go b/versionmgr/commit.go index 31fe5d74..ae1003cb 100644 --- a/versionmgr/commit.go +++ b/versionmgr/commit.go @@ -19,6 +19,7 @@ var ( commitLog = logging.Logger("commit") ) +// CommitOp used to wrap some function for commit, todo not easy to use, optimize it type CommitOp struct { commit *models.Commit @@ -27,6 +28,7 @@ type CommitOp struct { wip models.IWipRepo } +// NewCommitOp create commit operation with repo and exit commit, if operate with new repo, set commit arguments to nil func NewCommitOp(repo models.IRepo, commit *models.Commit) *CommitOp { return &CommitOp{ commit: commit, @@ -36,10 +38,13 @@ func NewCommitOp(repo models.IRepo, commit *models.Commit) *CommitOp { } } +// Commit return commit func (commitOp *CommitOp) Commit() *models.Commit { return commitOp.commit } +// AddCommit append a new commit to current head, read changes from wip, than create a new commit with parent point to current head, +// and replace tree hash with wip's currentTreeHash. func (commitOp *CommitOp) AddCommit(ctx context.Context, committer *models.User, wipID uuid.UUID, msg string) (*CommitOp, error) { wip, err := commitOp.wip.Get(ctx, &models.GetWipParam{ ID: wipID, @@ -97,6 +102,7 @@ func (commitOp *CommitOp) AddCommit(ctx context.Context, committer *models.User, }, nil } +// DiffCommit find file changes in two commit func (commitOp *CommitOp) DiffCommit(ctx context.Context, toCommitID hash.Hash) (*Changes, error) { fromNode, err := NewTreeNode(ctx, models.TreeEntry{ Name: "", @@ -129,6 +135,7 @@ func (commitOp *CommitOp) DiffCommit(ctx context.Context, toCommitID hash.Hash) return newChanges(changes), nil } +// Merge implement merge like git, docs https://en.wikipedia.org/wiki/Merge_(version_control) func (commitOp *CommitOp) Merge(ctx context.Context, merger *models.User, toMergeCommitHash hash.Hash, msg string, resolver ConflictResolver) (*models.Commit, error) { toMergeCommit, err := commitOp.object.Commit(ctx, toMergeCommitHash) From 5456cc40b53414663e2fdcd1c33c60741c4ff756 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Mon, 11 Dec 2023 16:55:07 +0800 Subject: [PATCH 060/210] feat: imple getobject and delete object --- api/custom_response.go | 3 + api/jiaozifs.gen.go | 161 ++++++++++++++++++++------------------- api/swagger.yml | 21 ++--- controller/object_ctl.go | 157 ++++++++++++++++++++++++++++++++------ versionmgr/worktree.go | 54 +++++++++++++ 5 files changed, 288 insertions(+), 108 deletions(-) diff --git a/api/custom_response.go b/api/custom_response.go index cf3a5fac..e0d848a3 100644 --- a/api/custom_response.go +++ b/api/custom_response.go @@ -38,3 +38,6 @@ func (response *JiaozifsResponse) CodeMsg(code int, msg string) { response.WriteHeader(code) _, _ = response.Write([]byte(msg)) } +func (response *JiaozifsResponse) Code(code int) { + response.WriteHeader(code) +} diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 90f3d725..6b3e1bde 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -91,6 +91,9 @@ type LoginJSONBody struct { // DeleteObjectParams defines parameters for DeleteObject. type DeleteObjectParams struct { + // WipID working in process + WipID openapi_types.UUID `form:"wipID" json:"wipID"` + // Branch branch to the ref Branch string `form:"branch" json:"branch"` @@ -100,8 +103,6 @@ type DeleteObjectParams struct { // GetObjectParams defines parameters for GetObject. type GetObjectParams struct { - Presign *bool `form:"presign,omitempty" json:"presign,omitempty"` - // Branch branch to the ref Branch string `form:"branch" json:"branch"` @@ -132,8 +133,8 @@ type UploadObjectMultipartBody struct { // UploadObjectParams defines parameters for UploadObject. type UploadObjectParams struct { - // StorageClass Deprecated, this capability will not be supported in future releases. - StorageClass *string `form:"storageClass,omitempty" json:"storageClass,omitempty"` + // WipID working in process + WipID openapi_types.UUID `form:"wipID" json:"wipID"` // Branch branch to the ref Branch string `form:"branch" json:"branch"` @@ -520,6 +521,18 @@ func NewDeleteObjectRequest(server string, user string, repository string, param if params != nil { queryValues := queryURL.Query() + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "wipID", runtime.ParamLocationQuery, params.WipID); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "branch", runtime.ParamLocationQuery, params.Branch); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { @@ -591,22 +604,6 @@ func NewGetObjectRequest(server string, user string, repository string, params * if params != nil { queryValues := queryURL.Query() - if params.Presign != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "presign", runtime.ParamLocationQuery, *params.Presign); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "branch", runtime.ParamLocationQuery, params.Branch); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { @@ -779,20 +776,16 @@ func NewUploadObjectRequestWithBody(server string, user string, repository strin if params != nil { queryValues := queryURL.Query() - if params.StorageClass != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "storageClass", runtime.ParamLocationQuery, *params.StorageClass); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "wipID", runtime.ParamLocationQuery, params.WipID); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) } } - } if queryFrag, err := runtime.StyleParamWithLocation("form", true, "branch", runtime.ParamLocationQuery, params.Branch); err != nil { @@ -1540,6 +1533,21 @@ func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.R // Parameter object where we will unmarshal all parameters from the context var params DeleteObjectParams + // ------------- Required query parameter "wipID" ------------- + + if paramValue := r.URL.Query().Get("wipID"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "wipID"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "wipID", r.URL.Query(), ¶ms.WipID) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "wipID", Err: err}) + return + } + // ------------- Required query parameter "branch" ------------- if paramValue := r.URL.Query().Get("branch"); paramValue != "" { @@ -1612,14 +1620,6 @@ func (siw *ServerInterfaceWrapper) GetObject(w http.ResponseWriter, r *http.Requ // Parameter object where we will unmarshal all parameters from the context var params GetObjectParams - // ------------- Optional query parameter "presign" ------------- - - err = runtime.BindQueryParameter("form", true, false, "presign", r.URL.Query(), ¶ms.Presign) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "presign", Err: err}) - return - } - // ------------- Required query parameter "branch" ------------- if paramValue := r.URL.Query().Get("branch"); paramValue != "" { @@ -1806,11 +1806,18 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R // Parameter object where we will unmarshal all parameters from the context var params UploadObjectParams - // ------------- Optional query parameter "storageClass" ------------- + // ------------- Required query parameter "wipID" ------------- + + if paramValue := r.URL.Query().Get("wipID"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "wipID"}) + return + } - err = runtime.BindQueryParameter("form", true, false, "storageClass", r.URL.Query(), ¶ms.StorageClass) + err = runtime.BindQueryParameter("form", true, true, "wipID", r.URL.Query(), ¶ms.WipID) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "storageClass", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "wipID", Err: err}) return } @@ -2037,40 +2044,40 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "73qPL0aLfwUAAP//iqm+sXAhAAA=", + "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", + "1X8CAAD//16H5RCTIQAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 21c84163..e47f2ebe 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -254,11 +254,6 @@ paths: schema: type: string pattern: '^bytes=((\d*-\d*,? ?)+)$' - - in: query - name: presign - required: false - schema: - type: boolean responses: 200: description: object content @@ -395,12 +390,12 @@ paths: parameters: - in: query - name: storageClass - description: Deprecated, this capability will not be supported in future releases. - required: false - deprecated: true + name: wipID + description: working in process + required: true schema: type: string + format: uuid - in: header name: If-None-Match description: | @@ -436,6 +431,14 @@ paths: - objects operationId: deleteObject summary: delete object. Missing objects will not return a NotFound error. + parameters: + - in: query + name: wipID + description: working in process + required: true + schema: + type: string + format: uuid responses: 204: description: object deleted successfully diff --git a/controller/object_ctl.go b/controller/object_ctl.go index 38e20eee..ddddcae6 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -2,6 +2,7 @@ package controller import ( "context" + "errors" "fmt" "io" "mime" @@ -9,9 +10,10 @@ import ( "net/http" "time" + logging "github.com/ipfs/go-log/v2" + "github.com/go-openapi/swag" "github.com/jiaozifs/jiaozifs/models/filemode" - "github.com/jiaozifs/jiaozifs/versionmgr" "github.com/jiaozifs/jiaozifs/block" @@ -25,6 +27,8 @@ import ( "go.uber.org/fx" ) +var objLog = logging.Logger("object_ctl") + type ObjectController struct { fx.In @@ -33,14 +37,137 @@ type ObjectController struct { Repo models.IRepo } -func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, user string, repository string, params api.DeleteObjectParams) { //nolint - //TODO implement me - panic("implement me") +func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, userName string, repositoryName string, params api.DeleteObjectParams) { //nolint + user, err := oct.Repo.UserRepo().Get(ctx, &models.GetUserParam{Name: utils.String(userName)}) + if err != nil { + w.Error(err) + return + } + + repository, err := oct.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ + CreateID: user.ID, + Name: utils.String(repositoryName), + }) + if err != nil { + w.Error(err) + return + } + + ref, err := oct.Repo.RefRepo().Get(ctx, &models.GetRefParams{ + RepositoryID: repository.ID, + Name: utils.String(params.Branch), + }) + if err != nil { + w.Error(err) + return + } + commit, err := oct.Repo.ObjectRepo().Commit(ctx, ref.CommitHash) + if err != nil { + w.Error(err) + return + } + + workTree, err := versionmgr.NewWorkTree(ctx, oct.Repo.ObjectRepo(), models.NewRootTreeEntry(commit.TreeHash)) + if err != nil { + w.Error(err) + return + } + + err = workTree.RemoveEntry(ctx, params.Path) + if errors.Is(err, versionmgr.ErrPathNotFound) { + w.Code(http.StatusNotFound) + return + } + + err = oct.Repo.WipRepo().UpdateCurrentHash(ctx, params.WipID, workTree.Root().Hash()) + if err != nil { + w.Error(err) + return + } + w.OK() } -func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, user string, repository string, params api.GetObjectParams) { //nolint - //TODO implement me - panic("implement me") +func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, userName string, repositoryName string, params api.GetObjectParams) { //nolint + user, err := oct.Repo.UserRepo().Get(ctx, &models.GetUserParam{Name: utils.String(userName)}) + if err != nil { + w.Error(err) + return + } + + repository, err := oct.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ + CreateID: user.ID, + Name: utils.String(repositoryName), + }) + if err != nil { + w.Error(err) + return + } + + ref, err := oct.Repo.RefRepo().Get(ctx, &models.GetRefParams{ + RepositoryID: repository.ID, + Name: utils.String(params.Branch), + }) + if err != nil { + w.Error(err) + return + } + commit, err := oct.Repo.ObjectRepo().Commit(ctx, ref.CommitHash) + if err != nil { + w.Error(err) + return + } + + workTree, err := versionmgr.NewWorkTree(ctx, oct.Repo.ObjectRepo(), models.NewRootTreeEntry(commit.TreeHash)) + if err != nil { + w.Error(err) + return + } + + blob, name, err := workTree.FindBlob(ctx, params.Path) + if err != nil { + w.Error(err) + return + } + reader, err := workTree.ReadBlob(ctx, oct.BlockAdapter, blob, params.Range) + if err != nil { + w.Error(err) + return + } + defer reader.Close() //nolint + // handle partial response if byte range supplied + if params.Range != nil { + rng, err := httputil.ParseRange(*params.Range, blob.Size) + if err != nil { + w.CodeMsg(http.StatusRequestedRangeNotSatisfiable, "Requested Range Not Satisfiable") + return + } + w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", rng.StartOffset, rng.EndOffset, blob.Size)) + w.Header().Set("Content-Length", fmt.Sprintf("%d", rng.EndOffset-rng.StartOffset+1)) + w.Code(http.StatusPartialContent) + } else { + w.Header().Set("Content-Length", fmt.Sprint(blob.Size)) + } + + etag := httputil.ETag(blob.Hash.Hex()) + w.Header().Set("ETag", etag) + lastModified := httputil.HeaderTimestamp(blob.CreatedAt) + w.Header().Set("Last-Modified", lastModified) + w.Header().Set("Content-Type", httputil.ExtensionsByType(name)) + // for security, make sure the browser and any proxies en route don't cache the response + w.Header().Set("Cache-Control", "no-store, must-revalidate") + w.Header().Set("Expires", "0") + w.Header().Set("X-Content-Type-Options", "nosniff") + w.Header().Set("X-Frame-Options", "SAMEORIGIN") + w.Header().Set("Content-Security-Policy", "default-src 'none'") + _, err = io.Copy(w, reader) + if err != nil { + objLog.With( + "user", userName, + "repo", repositoryName, + "path", params.Path). + Debugf("GetObject copy content %v", err) + + } } func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, userName string, repository string, params api.HeadObjectParams) { //nolint @@ -173,22 +300,8 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes var response api.ObjectStats err = oct.Repo.Transaction(ctx, func(dRepo models.IRepo) error { - user, err := dRepo.UserRepo().Get(ctx, &models.GetUserParam{Name: utils.String(userName)}) - if err != nil { - return err - } - - repo, err := dRepo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ - CreateID: user.ID, - Name: utils.String(repository), - }) - if err != nil { - return err - } - stash, err := dRepo.WipRepo().Get(ctx, &models.GetWipParam{ - RepositoryID: repo.ID, - CreateID: user.ID, + ID: params.WipID, }) if err != nil { return err diff --git a/versionmgr/worktree.go b/versionmgr/worktree.go index 6a7dc201..f34038b7 100644 --- a/versionmgr/worktree.go +++ b/versionmgr/worktree.go @@ -10,6 +10,8 @@ import ( "strings" "time" + "github.com/jiaozifs/jiaozifs/utils/httputil" + "github.com/go-git/go-git/v5/utils/merkletrie" "github.com/jiaozifs/jiaozifs/block" @@ -72,6 +74,40 @@ func (workTree *WorkTree) Root() *TreeNode { return workTree.root } +// ReadBlob read blob content with range +func (workTree *WorkTree) ReadBlob(ctx context.Context, adapter block.Adapter, blob *models.Blob, rangeSpec *string) (io.ReadCloser, error) { + address := pathutil.PathOfHash(blob.Hash) + pointer := block.ObjectPointer{ + StorageNamespace: adapter.BlockstoreType() + "://", + IdentifierType: block.IdentifierTypeRelative, + Identifier: address, + } + + // setup response + var reader io.ReadCloser + + // handle partial response if byte range supplied + if rangeSpec != nil { + rng, err := httputil.ParseRange(*rangeSpec, blob.Size) + if err != nil { + return nil, err + } + reader, err = adapter.GetRange(ctx, pointer, rng.StartOffset, rng.EndOffset) + if err != nil { + return nil, err + } + + } else { + var err error + reader, err = adapter.Get(ctx, pointer, blob.Size) + if err != nil { + return nil, err + } + } + return reader, nil +} + +// WriteBlob write blob content to storage func (workTree *WorkTree) WriteBlob(ctx context.Context, adapter block.Adapter, body io.Reader, contentLength int64, opts block.PutOpts) (*models.Blob, error) { // handle the upload itself hashReader := hash.NewHashingReader(body, hash.Md5) @@ -486,6 +522,24 @@ func (workTree *WorkTree) Ls(ctx context.Context, fullPath string) ([]models.Tre return lastNode.Node().SubObjects, nil } +func (workTree *WorkTree) FindBlob(ctx context.Context, fullPath string) (*models.Blob, string, error) { + existNode, missingPath, err := workTree.MatchPath(ctx, fullPath) + if err != nil { + return nil, "", err + } + + if len(missingPath) > 0 { + return nil, "", ErrPathNotFound + } + + lastNode := existNode[len(existNode)-1] + if lastNode.Node().Type != models.BlobObject { + return nil, "", ErrPathNotFound + } + + return lastNode.Node().Blob(), lastNode.Entry().Name, nil +} + func (workTree *WorkTree) ApplyOneChange(ctx context.Context, change IChange) error { action, err := change.Action() if err != nil { From 24a532db8540353e42a93b8e288248cfc20bc3cf Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Mon, 11 Dec 2023 17:20:05 +0800 Subject: [PATCH 061/210] feat: add test for readblob and findblob --- versionmgr/worktree.go | 1 + versionmgr/worktree_test.go | 52 +++++++++++++++++++++++-------------- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/versionmgr/worktree.go b/versionmgr/worktree.go index f34038b7..9caa5b9c 100644 --- a/versionmgr/worktree.go +++ b/versionmgr/worktree.go @@ -143,6 +143,7 @@ func (workTree *WorkTree) WriteBlob(ctx context.Context, adapter block.Adapter, } return &models.Blob{ + Type: models.BlobObject, Hash: hash, Size: hashReader.CopiedSize, }, nil diff --git a/versionmgr/worktree_test.go b/versionmgr/worktree_test.go index a760437b..3def9149 100644 --- a/versionmgr/worktree_test.go +++ b/versionmgr/worktree_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "errors" + "io" "testing" "github.com/jiaozifs/jiaozifs/utils/hash" @@ -27,16 +28,22 @@ func TestTreeOpWriteBlob(t *testing.T) { adapter := mem.New(ctx) objRepo := models.NewObjectRepo(db) - treeOp, err := NewWorkTree(ctx, objRepo, EmptyDirEntry) + workTree, err := NewWorkTree(ctx, objRepo, EmptyDirEntry) require.NoError(t, err) binary := []byte("Build simple, secure, scalable systems with Go") bLen := int64(len(binary)) r := bytes.NewReader(binary) - blob, err := treeOp.WriteBlob(ctx, adapter, r, bLen, block.PutOpts{}) + blob, err := workTree.WriteBlob(ctx, adapter, r, bLen, block.PutOpts{}) require.NoError(t, err) assert.Equal(t, bLen, blob.Size) assert.Equal(t, "f3b39786b86a96372589aa1166966643", blob.Hash.Hex()) + + reader, err := workTree.ReadBlob(ctx, adapter, blob, nil) + require.NoError(t, err) + content, err := io.ReadAll(reader) + require.NoError(t, err) + require.Equal(t, binary, content) } func TestTreeOpTreeOp(t *testing.T) { @@ -47,45 +54,52 @@ func TestTreeOpTreeOp(t *testing.T) { adapter := mem.New(ctx) objRepo := models.NewObjectRepo(db) - treeOp, err := NewWorkTree(ctx, objRepo, EmptyDirEntry) + workTree, err := NewWorkTree(ctx, objRepo, EmptyDirEntry) require.NoError(t, err) binary := []byte("Build simple, secure, scalable systems with Go") bLen := int64(len(binary)) r := bytes.NewReader(binary) - blob, err := treeOp.WriteBlob(ctx, adapter, r, bLen, block.PutOpts{}) + blob, err := workTree.WriteBlob(ctx, adapter, r, bLen, block.PutOpts{}) require.NoError(t, err) - err = treeOp.AddLeaf(ctx, "a/b/c.txt", blob) + err = workTree.AddLeaf(ctx, "a/b/c.txt", blob) require.NoError(t, err) - require.Equal(t, "3bf643c30934d121ee45d413b165f135", hash.Hash(treeOp.Root().Hash()).Hex()) + require.Equal(t, "3bf643c30934d121ee45d413b165f135", hash.Hash(workTree.Root().Hash()).Hex()) //add again expect get an error - err = treeOp.AddLeaf(ctx, "a/b/c.txt", blob) + err = workTree.AddLeaf(ctx, "a/b/c.txt", blob) require.True(t, errors.Is(err, ErrEntryExit)) //update path binary = []byte(`“At the time, no single team member knew Go, but within a month, everyone was writing in Go and we were building out the endpoints. ”`) bLen = int64(len(binary)) r = bytes.NewReader(binary) - blob, err = treeOp.WriteBlob(ctx, adapter, r, bLen, block.PutOpts{}) + blob, err = workTree.WriteBlob(ctx, adapter, r, bLen, block.PutOpts{}) require.NoError(t, err) - err = treeOp.ReplaceLeaf(ctx, "a/b/c.txt", blob) + err = workTree.ReplaceLeaf(ctx, "a/b/c.txt", blob) require.NoError(t, err) - require.Equal(t, "8856b15f0f6c7ad21bfabe812df69e83", hash.Hash(treeOp.Root().Hash()).Hex()) + require.Equal(t, "8856b15f0f6c7ad21bfabe812df69e83", hash.Hash(workTree.Root().Hash()).Hex()) + { + //find blob + findBlob, name, err := workTree.FindBlob(ctx, "a/b/c.txt") + require.NoError(t, err) + require.Equal(t, "c.txt", name) + require.Equal(t, blob.Hash.Hex(), findBlob.Hash.Hex()) + } { //add another branch - err = treeOp.AddLeaf(ctx, "a/b/d.txt", blob) + err = workTree.AddLeaf(ctx, "a/b/d.txt", blob) require.NoError(t, err) - require.Equal(t, "225f0ca6233681a441969922a7425db2", hash.Hash(treeOp.Root().Hash()).Hex()) + require.Equal(t, "225f0ca6233681a441969922a7425db2", hash.Hash(workTree.Root().Hash()).Hex()) } { //check fs structure - rootDir, err := objRepo.TreeNode(ctx, treeOp.Root().Hash()) + rootDir, err := objRepo.TreeNode(ctx, workTree.Root().Hash()) require.NoError(t, err) require.Len(t, rootDir.SubObjects, 1) require.Equal(t, "a", rootDir.SubObjects[0].Name) @@ -104,25 +118,25 @@ func TestTreeOpTreeOp(t *testing.T) { { //check ls - subObjects, err := treeOp.Ls(ctx, "a") + subObjects, err := workTree.Ls(ctx, "a") require.NoError(t, err) require.Len(t, subObjects, 1) require.Equal(t, "b", subObjects[0].Name) - subObjects, err = treeOp.Ls(ctx, "a/b") + subObjects, err = workTree.Ls(ctx, "a/b") require.NoError(t, err) require.Len(t, subObjects, 2) require.Equal(t, "c.txt", subObjects[0].Name) require.Equal(t, "d.txt", subObjects[1].Name) } - err = treeOp.RemoveEntry(ctx, "a/b/c.txt") + err = workTree.RemoveEntry(ctx, "a/b/c.txt") require.NoError(t, err) - require.Equal(t, "f90e2d306ad172824fa171b9e0d9e133", hash.Hash(treeOp.Root().Hash()).Hex()) + require.Equal(t, "f90e2d306ad172824fa171b9e0d9e133", hash.Hash(workTree.Root().Hash()).Hex()) - err = treeOp.RemoveEntry(ctx, "a/b/d.txt") + err = workTree.RemoveEntry(ctx, "a/b/d.txt") require.NoError(t, err) - require.Equal(t, "", hash.Hash(treeOp.Root().Hash()).Hex()) + require.Equal(t, "", hash.Hash(workTree.Root().Hash()).Hex()) } func TestRemoveEntry(t *testing.T) { From 9b675805c000cd1948f107202f1af748c19c1c57 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Mon, 11 Dec 2023 19:58:45 +0800 Subject: [PATCH 062/210] chore: ignore gen code --- codecov.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 codecov.yml diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..23972b97 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,2 @@ +ignore: + - "**/*.gen.go" From 8f81d68d966b013dd20c17abc6b4a50431983807 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Mon, 11 Dec 2023 21:59:12 +0800 Subject: [PATCH 063/210] feat: add api for wip/commit --- api/api_impl/impl.go | 2 + api/custom_response.go | 36 +- api/custom_response_test.go | 104 +++ api/docs.go | 1 + api/jiaozifs.gen.go | 1700 ++++++++++++++++++++++++++++++++--- api/resp.gen.go | 80 ++ api/swagger.yml | 394 +++++++- cmd/helper.go | 6 +- controller/commit_ctl.go | 117 +++ controller/object_ctl.go | 6 +- controller/wip_ctl.go | 220 +++++ go.mod | 2 + go.sum | 3 + models/error.go | 7 + models/ref.go | 2 +- models/repository.go | 2 +- models/wip.go | 84 +- models/wip_test.go | 44 +- versionmgr/changes.go | 18 +- versionmgr/changes_test.go | 47 + versionmgr/commit_test.go | 2 +- 21 files changed, 2750 insertions(+), 127 deletions(-) create mode 100644 api/custom_response_test.go create mode 100644 api/resp.gen.go create mode 100644 controller/commit_ctl.go create mode 100644 controller/wip_ctl.go create mode 100644 models/error.go diff --git a/api/api_impl/impl.go b/api/api_impl/impl.go index 060b6d4f..6ebebe18 100644 --- a/api/api_impl/impl.go +++ b/api/api_impl/impl.go @@ -14,4 +14,6 @@ type APIController struct { controller.VersionController controller.ObjectController controller.UserController + controller.WipController + controller.CommitController } diff --git a/api/custom_response.go b/api/custom_response.go index e0d848a3..f0c20860 100644 --- a/api/custom_response.go +++ b/api/custom_response.go @@ -9,35 +9,53 @@ type JiaozifsResponse struct { http.ResponseWriter } -func (response *JiaozifsResponse) JSON(v interface{}) { +// JSON convert object to json format and write to response, +// if not specific code, default code is 200. given code will +// overwrite default code, if more than one code, the first one will be used. +func (response *JiaozifsResponse) JSON(v any, code ...int) { + if len(code) == 0 { + response.WriteHeader(http.StatusOK) + } else { + response.WriteHeader(code[0]) + } response.Header().Set("Content-Type", "application/json") - response.WriteHeader(http.StatusOK) - err := json.NewEncoder(response).Encode(v) + err := json.NewEncoder(response.ResponseWriter).Encode(v) if err != nil { response.Error(err) return } } +// OK response with 200 func (response *JiaozifsResponse) OK() { response.WriteHeader(http.StatusOK) } +// NotFound response with 404 +func (response *JiaozifsResponse) NotFound() { + response.WriteHeader(http.StatusNotFound) +} + +// Error response with 500 and error message func (response *JiaozifsResponse) Error(err error) { response.WriteHeader(http.StatusInternalServerError) _, _ = response.Write([]byte(err.Error())) } -func (response *JiaozifsResponse) String(msg string) { +// String response and string +// if not specific code, default code is 200. given code will +// overwrite default code, if more than one code, the first one will be used. +func (response *JiaozifsResponse) String(msg string, code ...int) { + if len(code) == 0 { + response.WriteHeader(http.StatusOK) + } else { + response.WriteHeader(code[0]) + } response.Header().Set("Content-Type", "text/plain;charset=UTF-8") - response.WriteHeader(http.StatusOK) _, _ = response.Write([]byte(msg)) } -func (response *JiaozifsResponse) CodeMsg(code int, msg string) { - response.WriteHeader(code) - _, _ = response.Write([]byte(msg)) -} +// Code response with uncommon code func (response *JiaozifsResponse) Code(code int) { response.WriteHeader(code) } diff --git a/api/custom_response_test.go b/api/custom_response_test.go new file mode 100644 index 00000000..3b9572f4 --- /dev/null +++ b/api/custom_response_test.go @@ -0,0 +1,104 @@ +package api + +import ( + "fmt" + "net/http" + "testing" + + "go.uber.org/mock/gomock" +) + +func TestJiaozifsResponse(t *testing.T) { + t.Run("not found", func(t *testing.T) { + ctrl := gomock.NewController(t) + resp := NewMockResponseWriter(ctrl) + jzResp := JiaozifsResponse{resp} + + resp.EXPECT().WriteHeader(http.StatusNotFound) + jzResp.NotFound() + }) + + t.Run("ok", func(t *testing.T) { + ctrl := gomock.NewController(t) + resp := NewMockResponseWriter(ctrl) + jzResp := JiaozifsResponse{resp} + + resp.EXPECT().WriteHeader(http.StatusOK) + jzResp.OK() + }) + t.Run("code", func(t *testing.T) { + ctrl := gomock.NewController(t) + resp := NewMockResponseWriter(ctrl) + jzResp := JiaozifsResponse{resp} + + resp.EXPECT().WriteHeader(http.StatusCreated) + jzResp.Code(http.StatusCreated) + }) + t.Run("error", func(t *testing.T) { + ctrl := gomock.NewController(t) + resp := NewMockResponseWriter(ctrl) + jzResp := JiaozifsResponse{resp} + + resp.EXPECT().WriteHeader(http.StatusInternalServerError) + resp.EXPECT().Write([]byte("mock")) + jzResp.Error(fmt.Errorf("mock")) + }) + + t.Run("string", func(t *testing.T) { + ctrl := gomock.NewController(t) + resp := NewMockResponseWriter(ctrl) + jzResp := JiaozifsResponse{resp} + + resp.EXPECT().WriteHeader(http.StatusOK) + resp.EXPECT().Header().DoAndReturn(func() http.Header { + return make(http.Header) + }) + resp.EXPECT().Write([]byte("test")) + jzResp.String("test") + }) + + t.Run("string with code", func(t *testing.T) { + ctrl := gomock.NewController(t) + resp := NewMockResponseWriter(ctrl) + jzResp := JiaozifsResponse{resp} + + resp.EXPECT().WriteHeader(http.StatusCreated) + resp.EXPECT().Header().DoAndReturn(func() http.Header { + return make(http.Header) + }) + resp.EXPECT().Write([]byte("test")) + jzResp.String("test", http.StatusCreated) + }) + + t.Run("json", func(t *testing.T) { + ctrl := gomock.NewController(t) + resp := NewMockResponseWriter(ctrl) + jzResp := JiaozifsResponse{resp} + + resp.EXPECT().WriteHeader(http.StatusOK) + resp.EXPECT().Header().DoAndReturn(func() http.Header { + return make(http.Header) + }) + + resp.EXPECT().Write([]byte("{\"Name\":\"aa\"}\n")) + jzResp.JSON(struct { + Name string + }{Name: "aa"}) + }) + t.Run("json with code", func(t *testing.T) { + ctrl := gomock.NewController(t) + resp := NewMockResponseWriter(ctrl) + jzResp := JiaozifsResponse{resp} + + resp.EXPECT().WriteHeader(http.StatusCreated) + resp.EXPECT().Header().DoAndReturn(func() http.Header { + return make(http.Header) + }) + + resp.EXPECT().Write([]byte("{\"Name\":\"aa\"}\n")) + jzResp.JSON(struct { + Name string + }{Name: "aa"}, http.StatusCreated) + }) + +} diff --git a/api/docs.go b/api/docs.go index 7e118509..29fc4491 100644 --- a/api/docs.go +++ b/api/docs.go @@ -2,3 +2,4 @@ package api //go:generate go run github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen -package api -templates ./tmpls -generate "types,client,chi-server,spec" -o jiaozifs.gen.go ./swagger.yml +//go:generate go run go.uber.org/mock/mockgen@latest --package=api --destination=resp.gen.go net/http ResponseWriter diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 6b3e1bde..2dd56a48 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -37,6 +37,14 @@ type AuthenticationToken struct { TokenExpiration *int64 `json:"token_expiration,omitempty"` } +// Change defines model for Change. +type Change struct { + Action int `json:"Action"` + BaseHash *string `json:"BaseHash,omitempty"` + Path string `json:"Path"` + ToHash *string `json:"ToHash,omitempty"` +} + // ObjectStats defines model for ObjectStats. type ObjectStats struct { Checksum string `json:"checksum"` @@ -55,6 +63,13 @@ type ObjectStats struct { // ObjectUserMetadata defines model for ObjectUserMetadata. type ObjectUserMetadata map[string]string +// TreeEntry defines model for TreeEntry. +type TreeEntry struct { + Hash *string `json:"Hash,omitempty"` + Mode *uint32 `json:"Mode,omitempty"` + Name *string `json:"Name,omitempty"` +} + // UserInfo defines model for UserInfo. type UserInfo struct { CreatedAt *time.Time `json:"createdAt,omitempty"` @@ -83,12 +98,37 @@ type VersionResult struct { Version string `json:"version"` } +// Wip defines model for Wip. +type Wip struct { + BaseTree *string `json:"BaseTree,omitempty"` + CreateID *openapi_types.UUID `json:"CreateID,omitempty"` + CreatedAt *time.Time `json:"CreatedAt,omitempty"` + CurrentTree *string `json:"CurrentTree,omitempty"` + ID *openapi_types.UUID `json:"ID,omitempty"` + Name *string `json:"Name,omitempty"` + RepositoryID *openapi_types.UUID `json:"RepositoryID,omitempty"` + State *int `json:"State,omitempty"` + UpdatedAt *time.Time `json:"UpdatedAt,omitempty"` +} + // LoginJSONBody defines parameters for Login. type LoginJSONBody struct { Password string `json:"password"` Username string `json:"username"` } +// GetCommitDiffParams defines parameters for GetCommitDiff. +type GetCommitDiffParams struct { + // Path specific path, if not specific return entries in root + Path *string `form:"path,omitempty" json:"path,omitempty"` +} + +// GetEntriesInCommitParams defines parameters for GetEntriesInCommit. +type GetEntriesInCommitParams struct { + // Path specific path, if not specific return entries in root + Path *string `form:"path,omitempty" json:"path,omitempty"` +} + // DeleteObjectParams defines parameters for DeleteObject. type DeleteObjectParams struct { // WipID working in process @@ -147,6 +187,21 @@ type UploadObjectParams struct { IfNoneMatch *string `json:"If-None-Match,omitempty"` } +// CommitWipParams defines parameters for CommitWip. +type CommitWipParams struct { + // Msg commit message + Msg *string `form:"msg,omitempty" json:"msg,omitempty"` +} + +// CreateWipParams defines parameters for CreateWip. +type CreateWipParams struct { + // Name wip name + Name string `form:"name" json:"name"` + + // BaseCommitID base commit to create wip + BaseCommitID string `form:"baseCommitID" json:"baseCommitID"` +} + // LoginJSONRequestBody defines body for Login for application/json ContentType. type LoginJSONRequestBody LoginJSONBody @@ -242,6 +297,15 @@ type ClientInterface interface { // GetUserInfo request GetUserInfo(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetVersion request + GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetCommitDiff request + GetCommitDiff(ctx context.Context, user string, repository string, baseCommit string, toCommit string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetEntriesInCommit request + GetEntriesInCommit(ctx context.Context, user string, repository string, commitHash string, params *GetEntriesInCommitParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // DeleteObject request DeleteObject(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -254,8 +318,20 @@ type ClientInterface interface { // UploadObjectWithBody request with any body UploadObjectWithBody(ctx context.Context, user string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - // GetVersion request - GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // CommitWip request + CommitWip(ctx context.Context, user string, repository string, refID openapi_types.UUID, params *CommitWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // ListWip request + ListWip(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // DeleteWip request + DeleteWip(ctx context.Context, user string, repository string, refID openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetWip request + GetWip(ctx context.Context, user string, repository string, refID openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateWip request + CreateWip(ctx context.Context, user string, repository string, refID openapi_types.UUID, params *CreateWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) } func (c *Client) LoginWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -318,6 +394,42 @@ func (c *Client) GetUserInfo(ctx context.Context, reqEditors ...RequestEditorFn) return c.Client.Do(req) } +func (c *Client) GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetVersionRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetCommitDiff(ctx context.Context, user string, repository string, baseCommit string, toCommit string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetCommitDiffRequest(c.Server, user, repository, baseCommit, toCommit, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetEntriesInCommit(ctx context.Context, user string, repository string, commitHash string, params *GetEntriesInCommitParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetEntriesInCommitRequest(c.Server, user, repository, commitHash, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) DeleteObject(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewDeleteObjectRequest(c.Server, user, repository, params) if err != nil { @@ -366,8 +478,56 @@ func (c *Client) UploadObjectWithBody(ctx context.Context, user string, reposito return c.Client.Do(req) } -func (c *Client) GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetVersionRequest(c.Server) +func (c *Client) CommitWip(ctx context.Context, user string, repository string, refID openapi_types.UUID, params *CommitWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCommitWipRequest(c.Server, user, repository, refID, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) ListWip(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListWipRequest(c.Server, user, repository) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) DeleteWip(ctx context.Context, user string, repository string, refID openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteWipRequest(c.Server, user, repository, refID) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetWip(ctx context.Context, user string, repository string, refID openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetWipRequest(c.Server, user, repository, refID) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateWip(ctx context.Context, user string, repository string, refID openapi_types.UUID, params *CreateWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateWipRequest(c.Server, user, repository, refID, params) if err != nil { return nil, err } @@ -485,6 +645,180 @@ func NewGetUserInfoRequest(server string) (*http.Request, error) { return req, nil } +// NewGetVersionRequest generates requests for GetVersion +func NewGetVersionRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/version") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetCommitDiffRequest generates requests for GetCommitDiff +func NewGetCommitDiffRequest(server string, user string, repository string, baseCommit string, toCommit string, params *GetCommitDiffParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "baseCommit", runtime.ParamLocationPath, baseCommit) + if err != nil { + return nil, err + } + + var pathParam3 string + + pathParam3, err = runtime.StyleParamWithLocation("simple", false, "toCommit", runtime.ParamLocationPath, toCommit) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/%s/%s/commit/diff/%s/%s", pathParam0, pathParam1, pathParam2, pathParam3) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Path != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetEntriesInCommitRequest generates requests for GetEntriesInCommit +func NewGetEntriesInCommitRequest(server string, user string, repository string, commitHash string, params *GetEntriesInCommitParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "commitHash", runtime.ParamLocationPath, commitHash) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/%s/%s/commit/ls/%s", pathParam0, pathParam1, pathParam2) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Path != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewDeleteObjectRequest generates requests for DeleteObject func NewDeleteObjectRequest(server string, user string, repository string, params *DeleteObjectParams) (*http.Request, error) { var err error @@ -508,7 +842,7 @@ func NewDeleteObjectRequest(server string, user string, repository string, param return nil, err } - operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/%s/%s/object", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -591,7 +925,7 @@ func NewGetObjectRequest(server string, user string, repository string, params * return nil, err } - operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/%s/%s/object", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -677,7 +1011,7 @@ func NewHeadObjectRequest(server string, user string, repository string, params return nil, err } - operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/%s/%s/object", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -763,7 +1097,7 @@ func NewUploadObjectRequestWithBody(server string, user string, repository strin return nil, err } - operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/%s/%s/object", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -840,19 +1174,103 @@ func NewUploadObjectRequestWithBody(server string, user string, repository strin return req, nil } -// NewGetVersionRequest generates requests for GetVersion -func NewGetVersionRequest(server string) (*http.Request, error) { +// NewCommitWipRequest generates requests for CommitWip +func NewCommitWipRequest(server string, user string, repository string, refID openapi_types.UUID, params *CommitWipParams) (*http.Request, error) { var err error - serverURL, err := url.Parse(server) + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/version") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "refID", runtime.ParamLocationPath, refID) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/%s/%s/wip/commit/%s", pathParam0, pathParam1, pathParam2) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Msg != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "msg", runtime.ParamLocationQuery, *params.Msg); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("POST", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewListWipRequest generates requests for ListWip +func NewListWipRequest(server string, user string, repository string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/%s/%s/wip/list", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } queryURL, err := serverURL.Parse(operationPath) if err != nil { @@ -867,6 +1285,180 @@ func NewGetVersionRequest(server string) (*http.Request, error) { return req, nil } +// NewDeleteWipRequest generates requests for DeleteWip +func NewDeleteWipRequest(server string, user string, repository string, refID openapi_types.UUID) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "refID", runtime.ParamLocationPath, refID) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/%s/%s/wip/%s", pathParam0, pathParam1, pathParam2) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetWipRequest generates requests for GetWip +func NewGetWipRequest(server string, user string, repository string, refID openapi_types.UUID) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "refID", runtime.ParamLocationPath, refID) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/%s/%s/wip/%s", pathParam0, pathParam1, pathParam2) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewCreateWipRequest generates requests for CreateWip +func NewCreateWipRequest(server string, user string, repository string, refID openapi_types.UUID, params *CreateWipParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "refID", runtime.ParamLocationPath, refID) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/%s/%s/wip/%s", pathParam0, pathParam1, pathParam2) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "name", runtime.ParamLocationQuery, params.Name); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "baseCommitID", runtime.ParamLocationQuery, params.BaseCommitID); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("POST", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { for _, r := range c.RequestEditors { if err := r(ctx, req); err != nil { @@ -923,6 +1515,15 @@ type ClientWithResponsesInterface interface { // GetUserInfoWithResponse request GetUserInfoWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetUserInfoResponse, error) + // GetVersionWithResponse request + GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) + + // GetCommitDiffWithResponse request + GetCommitDiffWithResponse(ctx context.Context, user string, repository string, baseCommit string, toCommit string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*GetCommitDiffResponse, error) + + // GetEntriesInCommitWithResponse request + GetEntriesInCommitWithResponse(ctx context.Context, user string, repository string, commitHash string, params *GetEntriesInCommitParams, reqEditors ...RequestEditorFn) (*GetEntriesInCommitResponse, error) + // DeleteObjectWithResponse request DeleteObjectWithResponse(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) @@ -935,8 +1536,20 @@ type ClientWithResponsesInterface interface { // UploadObjectWithBodyWithResponse request with any body UploadObjectWithBodyWithResponse(ctx context.Context, user string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) - // GetVersionWithResponse request - GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) + // CommitWipWithResponse request + CommitWipWithResponse(ctx context.Context, user string, repository string, refID openapi_types.UUID, params *CommitWipParams, reqEditors ...RequestEditorFn) (*CommitWipResponse, error) + + // ListWipWithResponse request + ListWipWithResponse(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*ListWipResponse, error) + + // DeleteWipWithResponse request + DeleteWipWithResponse(ctx context.Context, user string, repository string, refID openapi_types.UUID, reqEditors ...RequestEditorFn) (*DeleteWipResponse, error) + + // GetWipWithResponse request + GetWipWithResponse(ctx context.Context, user string, repository string, refID openapi_types.UUID, reqEditors ...RequestEditorFn) (*GetWipResponse, error) + + // CreateWipWithResponse request + CreateWipWithResponse(ctx context.Context, user string, repository string, refID openapi_types.UUID, params *CreateWipParams, reqEditors ...RequestEditorFn) (*CreateWipResponse, error) } type LoginResponse struct { @@ -1004,6 +1617,72 @@ func (r GetUserInfoResponse) StatusCode() int { return 0 } +type GetVersionResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *VersionResult +} + +// Status returns HTTPResponse.Status +func (r GetVersionResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetVersionResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetCommitDiffResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]Change +} + +// Status returns HTTPResponse.Status +func (r GetCommitDiffResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetCommitDiffResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetEntriesInCommitResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]TreeEntry +} + +// Status returns HTTPResponse.Status +func (r GetEntriesInCommitResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetEntriesInCommitResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type DeleteObjectResponse struct { Body []byte HTTPResponse *http.Response @@ -1089,14 +1768,14 @@ func (r UploadObjectResponse) StatusCode() int { return 0 } -type GetVersionResponse struct { +type CommitWipResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *VersionResult + JSON200 *Wip } // Status returns HTTPResponse.Status -func (r GetVersionResponse) Status() string { +func (r CommitWipResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -1104,7 +1783,94 @@ func (r GetVersionResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetVersionResponse) StatusCode() int { +func (r CommitWipResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type ListWipResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]Wip +} + +// Status returns HTTPResponse.Status +func (r ListWipResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ListWipResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type DeleteWipResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r DeleteWipResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DeleteWipResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetWipResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Wip +} + +// Status returns HTTPResponse.Status +func (r GetWipResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetWipResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type CreateWipResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *Wip +} + +// Status returns HTTPResponse.Status +func (r CreateWipResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateWipResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -1154,6 +1920,33 @@ func (c *ClientWithResponses) GetUserInfoWithResponse(ctx context.Context, reqEd return ParseGetUserInfoResponse(rsp) } +// GetVersionWithResponse request returning *GetVersionResponse +func (c *ClientWithResponses) GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) { + rsp, err := c.GetVersion(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetVersionResponse(rsp) +} + +// GetCommitDiffWithResponse request returning *GetCommitDiffResponse +func (c *ClientWithResponses) GetCommitDiffWithResponse(ctx context.Context, user string, repository string, baseCommit string, toCommit string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*GetCommitDiffResponse, error) { + rsp, err := c.GetCommitDiff(ctx, user, repository, baseCommit, toCommit, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetCommitDiffResponse(rsp) +} + +// GetEntriesInCommitWithResponse request returning *GetEntriesInCommitResponse +func (c *ClientWithResponses) GetEntriesInCommitWithResponse(ctx context.Context, user string, repository string, commitHash string, params *GetEntriesInCommitParams, reqEditors ...RequestEditorFn) (*GetEntriesInCommitResponse, error) { + rsp, err := c.GetEntriesInCommit(ctx, user, repository, commitHash, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetEntriesInCommitResponse(rsp) +} + // DeleteObjectWithResponse request returning *DeleteObjectResponse func (c *ClientWithResponses) DeleteObjectWithResponse(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) { rsp, err := c.DeleteObject(ctx, user, repository, params, reqEditors...) @@ -1190,31 +1983,135 @@ func (c *ClientWithResponses) UploadObjectWithBodyWithResponse(ctx context.Conte return ParseUploadObjectResponse(rsp) } -// GetVersionWithResponse request returning *GetVersionResponse -func (c *ClientWithResponses) GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) { - rsp, err := c.GetVersion(ctx, reqEditors...) +// CommitWipWithResponse request returning *CommitWipResponse +func (c *ClientWithResponses) CommitWipWithResponse(ctx context.Context, user string, repository string, refID openapi_types.UUID, params *CommitWipParams, reqEditors ...RequestEditorFn) (*CommitWipResponse, error) { + rsp, err := c.CommitWip(ctx, user, repository, refID, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseCommitWipResponse(rsp) +} + +// ListWipWithResponse request returning *ListWipResponse +func (c *ClientWithResponses) ListWipWithResponse(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*ListWipResponse, error) { + rsp, err := c.ListWip(ctx, user, repository, reqEditors...) + if err != nil { + return nil, err + } + return ParseListWipResponse(rsp) +} + +// DeleteWipWithResponse request returning *DeleteWipResponse +func (c *ClientWithResponses) DeleteWipWithResponse(ctx context.Context, user string, repository string, refID openapi_types.UUID, reqEditors ...RequestEditorFn) (*DeleteWipResponse, error) { + rsp, err := c.DeleteWip(ctx, user, repository, refID, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteWipResponse(rsp) +} + +// GetWipWithResponse request returning *GetWipResponse +func (c *ClientWithResponses) GetWipWithResponse(ctx context.Context, user string, repository string, refID openapi_types.UUID, reqEditors ...RequestEditorFn) (*GetWipResponse, error) { + rsp, err := c.GetWip(ctx, user, repository, refID, reqEditors...) if err != nil { return nil, err } - return ParseGetVersionResponse(rsp) + return ParseGetWipResponse(rsp) +} + +// CreateWipWithResponse request returning *CreateWipResponse +func (c *ClientWithResponses) CreateWipWithResponse(ctx context.Context, user string, repository string, refID openapi_types.UUID, params *CreateWipParams, reqEditors ...RequestEditorFn) (*CreateWipResponse, error) { + rsp, err := c.CreateWip(ctx, user, repository, refID, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateWipResponse(rsp) +} + +// ParseLoginResponse parses an HTTP response from a LoginWithResponse call +func ParseLoginResponse(rsp *http.Response) (*LoginResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &LoginResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest AuthenticationToken + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseRegisterResponse parses an HTTP response from a RegisterWithResponse call +func ParseRegisterResponse(rsp *http.Response) (*RegisterResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &RegisterResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetUserInfoResponse parses an HTTP response from a GetUserInfoWithResponse call +func ParseGetUserInfoResponse(rsp *http.Response) (*GetUserInfoResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetUserInfoResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest UserInfo + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil } -// ParseLoginResponse parses an HTTP response from a LoginWithResponse call -func ParseLoginResponse(rsp *http.Response) (*LoginResponse, error) { +// ParseGetVersionResponse parses an HTTP response from a GetVersionWithResponse call +func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &LoginResponse{ + response := &GetVersionResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest AuthenticationToken + var dest VersionResult if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -1225,38 +2122,48 @@ func ParseLoginResponse(rsp *http.Response) (*LoginResponse, error) { return response, nil } -// ParseRegisterResponse parses an HTTP response from a RegisterWithResponse call -func ParseRegisterResponse(rsp *http.Response) (*RegisterResponse, error) { +// ParseGetCommitDiffResponse parses an HTTP response from a GetCommitDiffWithResponse call +func ParseGetCommitDiffResponse(rsp *http.Response) (*GetCommitDiffResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &RegisterResponse{ + response := &GetCommitDiffResponse{ Body: bodyBytes, HTTPResponse: rsp, } + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []Change + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + return response, nil } -// ParseGetUserInfoResponse parses an HTTP response from a GetUserInfoWithResponse call -func ParseGetUserInfoResponse(rsp *http.Response) (*GetUserInfoResponse, error) { +// ParseGetEntriesInCommitResponse parses an HTTP response from a GetEntriesInCommitWithResponse call +func ParseGetEntriesInCommitResponse(rsp *http.Response) (*GetEntriesInCommitResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetUserInfoResponse{ + response := &GetEntriesInCommitResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest UserInfo + var dest []TreeEntry if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -1341,22 +2248,90 @@ func ParseUploadObjectResponse(rsp *http.Response) (*UploadObjectResponse, error return response, nil } -// ParseGetVersionResponse parses an HTTP response from a GetVersionWithResponse call -func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) { +// ParseCommitWipResponse parses an HTTP response from a CommitWipWithResponse call +func ParseCommitWipResponse(rsp *http.Response) (*CommitWipResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetVersionResponse{ + response := &CommitWipResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest VersionResult + var dest Wip + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseListWipResponse parses an HTTP response from a ListWipWithResponse call +func ParseListWipResponse(rsp *http.Response) (*ListWipResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListWipResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []Wip + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseDeleteWipResponse parses an HTTP response from a DeleteWipWithResponse call +func ParseDeleteWipResponse(rsp *http.Response) (*DeleteWipResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DeleteWipResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetWipResponse parses an HTTP response from a GetWipWithResponse call +func ParseGetWipResponse(rsp *http.Response) (*GetWipResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetWipResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Wip if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -1367,6 +2342,32 @@ func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) { return response, nil } +// ParseCreateWipResponse parses an HTTP response from a CreateWipWithResponse call +func ParseCreateWipResponse(rsp *http.Response) (*CreateWipResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateWipResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest Wip + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil +} + // ServerInterface represents all server handlers. type ServerInterface interface { // perform a login @@ -1378,21 +2379,42 @@ type ServerInterface interface { // get information of the currently logged-in user // (GET /auth/user) GetUserInfo(ctx context.Context, w *JiaozifsResponse, r *http.Request) + // return program and runtime version + // (GET /version) + GetVersion(ctx context.Context, w *JiaozifsResponse, r *http.Request) + // get commit differences + // (GET /{user}/{repository}/commit/diff/{baseCommit}/{toCommit}) + GetCommitDiff(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, baseCommit string, toCommit string, params GetCommitDiffParams) + // list entries in commit + // (GET /{user}/{repository}/commit/ls/{commitHash}) + GetEntriesInCommit(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, commitHash string, params GetEntriesInCommitParams) // delete object. Missing objects will not return a NotFound error. - // (DELETE /object/{user}/{repository}) + // (DELETE /{user}/{repository}/object) DeleteObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params DeleteObjectParams) // get object content - // (GET /object/{user}/{repository}) + // (GET /{user}/{repository}/object) GetObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params GetObjectParams) // check if object exists - // (HEAD /object/{user}/{repository}) + // (HEAD /{user}/{repository}/object) HeadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params HeadObjectParams) - // (POST /object/{user}/{repository}) + // (POST /{user}/{repository}/object) UploadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params UploadObjectParams) - // return program and runtime version - // (GET /version) - GetVersion(ctx context.Context, w *JiaozifsResponse, r *http.Request) + // commit working in process to branch + // (POST /{user}/{repository}/wip/commit/{refID}) + CommitWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, refID openapi_types.UUID, params CommitWipParams) + // list wip in specific project and user + // (GET /{user}/{repository}/wip/list) + ListWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string) + // remove working in process + // (DELETE /{user}/{repository}/wip/{refID}) + DeleteWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, refID openapi_types.UUID) + // get working in process + // (GET /{user}/{repository}/wip/{refID}) + GetWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, refID openapi_types.UUID) + // create working in process + // (POST /{user}/{repository}/wip/{refID}) + CreateWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, refID openapi_types.UUID, params CreateWipParams) } // Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. @@ -1417,32 +2439,74 @@ func (_ Unimplemented) GetUserInfo(ctx context.Context, w *JiaozifsResponse, r * w.WriteHeader(http.StatusNotImplemented) } +// return program and runtime version +// (GET /version) +func (_ Unimplemented) GetVersion(ctx context.Context, w *JiaozifsResponse, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get commit differences +// (GET /{user}/{repository}/commit/diff/{baseCommit}/{toCommit}) +func (_ Unimplemented) GetCommitDiff(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, baseCommit string, toCommit string, params GetCommitDiffParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// list entries in commit +// (GET /{user}/{repository}/commit/ls/{commitHash}) +func (_ Unimplemented) GetEntriesInCommit(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, commitHash string, params GetEntriesInCommitParams) { + w.WriteHeader(http.StatusNotImplemented) +} + // delete object. Missing objects will not return a NotFound error. -// (DELETE /object/{user}/{repository}) +// (DELETE /{user}/{repository}/object) func (_ Unimplemented) DeleteObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params DeleteObjectParams) { w.WriteHeader(http.StatusNotImplemented) } // get object content -// (GET /object/{user}/{repository}) +// (GET /{user}/{repository}/object) func (_ Unimplemented) GetObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params GetObjectParams) { w.WriteHeader(http.StatusNotImplemented) } // check if object exists -// (HEAD /object/{user}/{repository}) +// (HEAD /{user}/{repository}/object) func (_ Unimplemented) HeadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params HeadObjectParams) { w.WriteHeader(http.StatusNotImplemented) } -// (POST /object/{user}/{repository}) +// (POST /{user}/{repository}/object) func (_ Unimplemented) UploadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params UploadObjectParams) { w.WriteHeader(http.StatusNotImplemented) } -// return program and runtime version -// (GET /version) -func (_ Unimplemented) GetVersion(ctx context.Context, w *JiaozifsResponse, r *http.Request) { +// commit working in process to branch +// (POST /{user}/{repository}/wip/commit/{refID}) +func (_ Unimplemented) CommitWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, refID openapi_types.UUID, params CommitWipParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// list wip in specific project and user +// (GET /{user}/{repository}/wip/list) +func (_ Unimplemented) ListWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// remove working in process +// (DELETE /{user}/{repository}/wip/{refID}) +func (_ Unimplemented) DeleteWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, refID openapi_types.UUID) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get working in process +// (GET /{user}/{repository}/wip/{refID}) +func (_ Unimplemented) GetWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, refID openapi_types.UUID) { + w.WriteHeader(http.StatusNotImplemented) +} + +// create working in process +// (POST /{user}/{repository}/wip/{refID}) +func (_ Unimplemented) CreateWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, refID openapi_types.UUID, params CreateWipParams) { w.WriteHeader(http.StatusNotImplemented) } @@ -1482,17 +2546,161 @@ func (siw *ServerInterfaceWrapper) Register(w http.ResponseWriter, r *http.Reque handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) -} + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// GetUserInfo operation middleware +func (siw *ServerInterfaceWrapper) GetUserInfo(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetUserInfo(r.Context(), &JiaozifsResponse{w}, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// GetVersion operation middleware +func (siw *ServerInterfaceWrapper) GetVersion(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetVersion(r.Context(), &JiaozifsResponse{w}, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// GetCommitDiff operation middleware +func (siw *ServerInterfaceWrapper) GetCommitDiff(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "user" ------------- + var user string + + err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + // ------------- Path parameter "baseCommit" ------------- + var baseCommit string + + err = runtime.BindStyledParameterWithOptions("simple", "baseCommit", chi.URLParam(r, "baseCommit"), &baseCommit, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "baseCommit", Err: err}) + return + } + + // ------------- Path parameter "toCommit" ------------- + var toCommit string + + err = runtime.BindStyledParameterWithOptions("simple", "toCommit", chi.URLParam(r, "toCommit"), &toCommit, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "toCommit", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetCommitDiffParams + + // ------------- Optional query parameter "path" ------------- + + err = runtime.BindQueryParameter("form", true, false, "path", r.URL.Query(), ¶ms.Path) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetCommitDiff(r.Context(), &JiaozifsResponse{w}, r, user, repository, baseCommit, toCommit, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// GetEntriesInCommit operation middleware +func (siw *ServerInterfaceWrapper) GetEntriesInCommit(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "user" ------------- + var user string + + err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + // ------------- Path parameter "commitHash" ------------- + var commitHash string -// GetUserInfo operation middleware -func (siw *ServerInterfaceWrapper) GetUserInfo(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + err = runtime.BindStyledParameterWithOptions("simple", "commitHash", chi.URLParam(r, "commitHash"), &commitHash, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "commitHash", Err: err}) + return + } ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetEntriesInCommitParams + + // ------------- Optional query parameter "path" ------------- + + err = runtime.BindQueryParameter("form", true, false, "path", r.URL.Query(), ¶ms.Path) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetUserInfo(r.Context(), &JiaozifsResponse{w}, r) + siw.Handler.GetEntriesInCommit(r.Context(), &JiaozifsResponse{w}, r, user, repository, commitHash, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -1883,14 +3091,272 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R handler.ServeHTTP(w, r.WithContext(ctx)) } -// GetVersion operation middleware -func (siw *ServerInterfaceWrapper) GetVersion(w http.ResponseWriter, r *http.Request) { +// CommitWip operation middleware +func (siw *ServerInterfaceWrapper) CommitWip(w http.ResponseWriter, r *http.Request) { ctx := r.Context() + var err error + + // ------------- Path parameter "user" ------------- + var user string + + err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + // ------------- Path parameter "refID" ------------- + var refID openapi_types.UUID + + err = runtime.BindStyledParameterWithOptions("simple", "refID", chi.URLParam(r, "refID"), &refID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refID", Err: err}) + return + } + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params CommitWipParams + + // ------------- Optional query parameter "msg" ------------- + + err = runtime.BindQueryParameter("form", true, false, "msg", r.URL.Query(), ¶ms.Msg) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "msg", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetVersion(r.Context(), &JiaozifsResponse{w}, r) + siw.Handler.CommitWip(r.Context(), &JiaozifsResponse{w}, r, user, repository, refID, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// ListWip operation middleware +func (siw *ServerInterfaceWrapper) ListWip(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "user" ------------- + var user string + + err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListWip(r.Context(), &JiaozifsResponse{w}, r, user, repository) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// DeleteWip operation middleware +func (siw *ServerInterfaceWrapper) DeleteWip(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "user" ------------- + var user string + + err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + // ------------- Path parameter "refID" ------------- + var refID openapi_types.UUID + + err = runtime.BindStyledParameterWithOptions("simple", "refID", chi.URLParam(r, "refID"), &refID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refID", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteWip(r.Context(), &JiaozifsResponse{w}, r, user, repository, refID) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// GetWip operation middleware +func (siw *ServerInterfaceWrapper) GetWip(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "user" ------------- + var user string + + err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + // ------------- Path parameter "refID" ------------- + var refID openapi_types.UUID + + err = runtime.BindStyledParameterWithOptions("simple", "refID", chi.URLParam(r, "refID"), &refID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refID", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetWip(r.Context(), &JiaozifsResponse{w}, r, user, repository, refID) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// CreateWip operation middleware +func (siw *ServerInterfaceWrapper) CreateWip(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "user" ------------- + var user string + + err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + // ------------- Path parameter "refID" ------------- + var refID openapi_types.UUID + + err = runtime.BindStyledParameterWithOptions("simple", "refID", chi.URLParam(r, "refID"), &refID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refID", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params CreateWipParams + + // ------------- Required query parameter "name" ------------- + + if paramValue := r.URL.Query().Get("name"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "name"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "name", r.URL.Query(), ¶ms.Name) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "name", Err: err}) + return + } + + // ------------- Required query parameter "baseCommitID" ------------- + + if paramValue := r.URL.Query().Get("baseCommitID"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "baseCommitID"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "baseCommitID", r.URL.Query(), ¶ms.BaseCommitID) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "baseCommitID", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CreateWip(r.Context(), &JiaozifsResponse{w}, r, user, repository, refID, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -2023,19 +3489,40 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Get(options.BaseURL+"/auth/user", wrapper.GetUserInfo) }) r.Group(func(r chi.Router) { - r.Delete(options.BaseURL+"/object/{user}/{repository}", wrapper.DeleteObject) + r.Get(options.BaseURL+"/version", wrapper.GetVersion) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/object/{user}/{repository}", wrapper.GetObject) + r.Get(options.BaseURL+"/{user}/{repository}/commit/diff/{baseCommit}/{toCommit}", wrapper.GetCommitDiff) }) r.Group(func(r chi.Router) { - r.Head(options.BaseURL+"/object/{user}/{repository}", wrapper.HeadObject) + r.Get(options.BaseURL+"/{user}/{repository}/commit/ls/{commitHash}", wrapper.GetEntriesInCommit) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/object/{user}/{repository}", wrapper.UploadObject) + r.Delete(options.BaseURL+"/{user}/{repository}/object", wrapper.DeleteObject) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/version", wrapper.GetVersion) + r.Get(options.BaseURL+"/{user}/{repository}/object", wrapper.GetObject) + }) + r.Group(func(r chi.Router) { + r.Head(options.BaseURL+"/{user}/{repository}/object", wrapper.HeadObject) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/{user}/{repository}/object", wrapper.UploadObject) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/{user}/{repository}/wip/commit/{refID}", wrapper.CommitWip) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/{user}/{repository}/wip/list", wrapper.ListWip) + }) + r.Group(func(r chi.Router) { + r.Delete(options.BaseURL+"/{user}/{repository}/wip/{refID}", wrapper.DeleteWip) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/{user}/{repository}/wip/{refID}", wrapper.GetWip) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/{user}/{repository}/wip/{refID}", wrapper.CreateWip) }) return r @@ -2044,40 +3531,49 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "1X8CAAD//16H5RCTIQAA", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/resp.gen.go b/api/resp.gen.go new file mode 100644 index 00000000..b726ce38 --- /dev/null +++ b/api/resp.gen.go @@ -0,0 +1,80 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: net/http (interfaces: ResponseWriter) +// +// Generated by this command: +// +// mockgen --package=api --destination=resp.gen.go net/http ResponseWriter +// +// Package api is a generated GoMock package. +package api + +import ( + http "net/http" + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockResponseWriter is a mock of ResponseWriter interface. +type MockResponseWriter struct { + ctrl *gomock.Controller + recorder *MockResponseWriterMockRecorder +} + +// MockResponseWriterMockRecorder is the mock recorder for MockResponseWriter. +type MockResponseWriterMockRecorder struct { + mock *MockResponseWriter +} + +// NewMockResponseWriter creates a new mock instance. +func NewMockResponseWriter(ctrl *gomock.Controller) *MockResponseWriter { + mock := &MockResponseWriter{ctrl: ctrl} + mock.recorder = &MockResponseWriterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockResponseWriter) EXPECT() *MockResponseWriterMockRecorder { + return m.recorder +} + +// Header mocks base method. +func (m *MockResponseWriter) Header() http.Header { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Header") + ret0, _ := ret[0].(http.Header) + return ret0 +} + +// Header indicates an expected call of Header. +func (mr *MockResponseWriterMockRecorder) Header() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Header", reflect.TypeOf((*MockResponseWriter)(nil).Header)) +} + +// Write mocks base method. +func (m *MockResponseWriter) Write(arg0 []byte) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Write", arg0) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Write indicates an expected call of Write. +func (mr *MockResponseWriterMockRecorder) Write(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockResponseWriter)(nil).Write), arg0) +} + +// WriteHeader mocks base method. +func (m *MockResponseWriter) WriteHeader(arg0 int) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "WriteHeader", arg0) +} + +// WriteHeader indicates an expected call of WriteHeader. +func (mr *MockResponseWriterMockRecorder) WriteHeader(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteHeader", reflect.TypeOf((*MockResponseWriter)(nil).WriteHeader), arg0) +} diff --git a/api/swagger.yml b/api/swagger.yml index e47f2ebe..850fc129 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -9,7 +9,7 @@ info: version: 1.0.0 servers: - - url: "/api/v1" + - url: "http://localhost:34913/api/v1" description: jiaozifs server endpoint security: @@ -37,6 +37,134 @@ components: in: cookie name: saml_auth_session schemas: + Blob: + type: object + properties: + Hash: + type: string + Type: + type: integer + format: int8 + Size: + type: integer + format: int64 + CreatedAt: + type: string + format: date-time + UpdatedAt: + type: string + format: date-time + + Signature: + type: object + properties: + Name: + type: string + Email: + type: string + format: email + Timestamp: + type: string + format: date-time + + Commit: + type: object + properties: + Hash: + type: string + Type: + type: integer + format: int8 + Author: + $ref: "#/components/schemas/Signature" + Committer: + $ref: "#/components/schemas/Signature" + MergeTag: + type: string + Message: + type: string + TreeHash: + type: string + ParentHashes: + type: array + items: + type: string + CreatedAt: + type: string + format: date-time + UpdatedAt: + type: string + format: date-time + TreeEntry: + type: object + properties: + Name: + type: string + Hash: + type: string + Mode: + type: integer + format: uint32 + TreeNode: + type: object + properties: + Hash: + type: string + Type: + type: integer + format: int8 + SubObjects: + type: array + items: + $ref: "#/components/schemas/TreeEntry" + CreatedAt: + type: string + format: date-time + UpdatedAt: + type: string + format: date-time + Wip: + type: object + properties: + ID: + type: string + format: uuid + Name: + type: string + CurrentTree: + type: string + BaseTree: + type: string + RepositoryID: + type: string + format: uuid + State: + type: integer + format: int + CreateID: + type: string + format: uuid + CreatedAt: + type: string + format: date-time + UpdatedAt: + type: string + format: date-time + Change: + type: object + required: + - Path + - Action + properties: + Path: + type: string + Action: + type: integer + format: int + BaseHash: + type: string + ToHash: + type: string UserUpdate: type: object required: @@ -123,6 +251,7 @@ components: api_version: type: string description: runtime version + ObjectUserMetadata: type: object additionalProperties: @@ -191,6 +320,7 @@ components: type: integer minimum: 0 description: Maximal number of entries per page + Error: type: object required: @@ -216,7 +346,7 @@ paths: schema: $ref: "#/components/schemas/VersionResult" - /object/{user}/{repository}: + /{user}/{repository}/object: parameters: - in: path name: user @@ -451,6 +581,266 @@ paths: 420: description: too many requests + /{user}/{repository}/wip/{refID}: + parameters: + - in: path + name: user + required: true + schema: + type: string + - in: path + name: repository + required: true + schema: + type: string + - in: path + name: refID + required: true + schema: + type: string + format: uuid + get: + tags: + - wip + operationId: getWip + summary: get working in process + responses: + 200: + description: working in process + content: + application/json: + schema: + $ref: "#/components/schemas/Wip" + 400: + description: ValidationError + 401: + description: Unauthorized + 403: + description: Forbidden + delete: + tags: + - wip + operationId: deleteWip + summary: remove working in process + responses: + 200: + description: success to delete wip + 400: + description: ValidationError + 401: + description: Unauthorized + 403: + description: Forbidden + post: + tags: + - wip + operationId: createWip + summary: create working in process + parameters: + - in: query + name: name + description: wip name + required: true + schema: + type: string + - in: query + name: baseCommitID + description: base commit to create wip + required: true + schema: + type: string + responses: + 201: + description: working in process created + content: + application/json: + schema: + $ref: "#/components/schemas/Wip" + 400: + description: ValidationError + 401: + description: Unauthorized + 403: + description: Forbidden + 502: + description: internal server error + /{user}/{repository}/wip/commit/{refID}: + parameters: + - in: path + name: user + required: true + schema: + type: string + - in: path + name: repository + required: true + schema: + type: string + - in: path + name: refID + required: true + schema: + type: string + format: uuid + post: + tags: + - wip + operationId: commitWip + summary: commit working in process to branch + parameters: + - in: query + name: msg + description: commit message + required: false + schema: + type: string + responses: + 200: + description: commit success and response with new wip + content: + application/json: + schema: + $ref: "#/components/schemas/Wip" + 400: + description: ValidationError + 401: + description: Unauthorized + 403: + description: Forbidden + 502: + description: internal server error + + /{user}/{repository}/wip/list: + parameters: + - in: path + name: user + required: true + schema: + type: string + - in: path + name: repository + required: true + schema: + type: string + get: + tags: + - wip + operationId: listWip + summary: list wip in specific project and user + responses: + 200: + description: working in process + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Wip" + 400: + description: ValidationError + 401: + description: Unauthorized + 403: + description: Forbidden + + /{user}/{repository}/commit/ls/{commitHash}: + parameters: + - in: path + name: user + required: true + schema: + type: string + - in: path + name: repository + required: true + schema: + type: string + - in: path + name: commitHash + required: true + schema: + type: string + get: + tags: + - commit + operationId: getEntriesInCommit + summary: list entries in commit + parameters: + - in: query + name: path + description: specific path, if not specific return entries in root + required: false + schema: + type: string + responses: + 200: + description: commit + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/TreeEntry" + 400: + description: ValidationError + 401: + description: Unauthorized + 403: + description: Forbidden + 404: + description: url not found + + /{user}/{repository}/commit/diff/{baseCommit}/{toCommit}: + parameters: + - in: path + name: user + required: true + schema: + type: string + - in: path + name: repository + required: true + schema: + type: string + - in: path + name: baseCommit + required: true + schema: + type: string + - in: path + name: toCommit + required: true + schema: + type: string + get: + tags: + - commit + operationId: getCommitDiff + summary: get commit differences + parameters: + - in: query + name: path + description: specific path, if not specific return entries in root + required: false + schema: + type: string + responses: + 200: + description: commit diff + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Change" + 400: + description: ValidationError + 401: + description: Unauthorized + 503: + description: server internal error + + /auth/login: post: tags: diff --git a/cmd/helper.go b/cmd/helper.go index 080a3580..01f65289 100644 --- a/cmd/helper.go +++ b/cmd/helper.go @@ -16,5 +16,9 @@ func GetDefaultClient() (*api.Client, error) { if err != nil { return nil, err } - return api.NewClient(cfg.API.Listen + swagger.Servers[0].URL) + basePath, err := swagger.Servers[0].BasePath() + if err != nil { + return nil, err + } + return api.NewClient(cfg.API.Listen + basePath) } diff --git a/controller/commit_ctl.go b/controller/commit_ctl.go new file mode 100644 index 00000000..8ffe9c43 --- /dev/null +++ b/controller/commit_ctl.go @@ -0,0 +1,117 @@ +package controller + +import ( + "context" + "encoding/hex" + "errors" + "net/http" + "strings" + + "github.com/jiaozifs/jiaozifs/utils" + + "github.com/jiaozifs/jiaozifs/versionmgr" + + "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/models" + "go.uber.org/fx" +) + +type CommitController struct { + fx.In + + Repo models.IRepo +} + +func (commitCtl CommitController) GetEntriesInCommit(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, _ string, _ string, commitHashStr string, params api.GetEntriesInCommitParams) { + commitHash, err := hex.DecodeString(commitHashStr) + if err != nil { + w.Error(err) + return + } + commit, err := commitCtl.Repo.ObjectRepo().Get(ctx, &models.GetObjParams{ + Hash: commitHash, + }) + if err != nil { + w.Error(err) + return + } + + workTree, err := versionmgr.NewWorkTree(ctx, commitCtl.Repo.ObjectRepo(), models.NewRootTreeEntry(commit.TreeHash)) + if err != nil { + w.Error(err) + return + } + + path := "" + if params.Path != nil { + path = *params.Path + } + treeEntry, err := workTree.Ls(ctx, path) + if err != nil { + if errors.Is(err, versionmgr.ErrPathNotFound) { + w.NotFound() + return + } + w.Error(err) + return + } + w.JSON(treeEntry) +} + +func (commitCtl CommitController) GetCommitDiff(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, _ string, _ string, baseCommitStr string, toCommitStr string, params api.GetCommitDiffParams) { + bashCommitHash, err := hex.DecodeString(baseCommitStr) + if err != nil { + w.Error(err) + return + } + toCommitHash, err := hex.DecodeString(toCommitStr) + if err != nil { + w.Error(err) + return + } + + bashCommit, err := commitCtl.Repo.ObjectRepo().Commit(ctx, bashCommitHash) + if err != nil { + w.Error(err) + return + } + + path := "" + if params.Path != nil { + path = *params.Path + } + + commitOp := versionmgr.NewCommitOp(commitCtl.Repo, bashCommit) + changes, err := commitOp.DiffCommit(ctx, toCommitHash) + if err != nil { + w.Error(err) + return + } + + var changesResp []api.Change + err = changes.ForEach(func(change versionmgr.IChange) error { + action, err := change.Action() + if err != nil { + return err + } + fullPath := change.Path() + if strings.HasPrefix(fullPath, path) { + apiChange := api.Change{ + Action: int(action), + Path: fullPath, + } + if change.From() != nil { + apiChange.BaseHash = utils.String(hex.EncodeToString(change.From().Hash())) + } + if change.To() != nil { + apiChange.ToHash = utils.String(hex.EncodeToString(change.To().Hash())) + } + } + return nil + }) + if err != nil { + w.Error(err) + return + } + w.JSON(changesResp) +} diff --git a/controller/object_ctl.go b/controller/object_ctl.go index ddddcae6..768f6793 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -138,7 +138,7 @@ func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsRespon if params.Range != nil { rng, err := httputil.ParseRange(*params.Range, blob.Size) if err != nil { - w.CodeMsg(http.StatusRequestedRangeNotSatisfiable, "Requested Range Not Satisfiable") + w.String("Requested Range Not Satisfiable", http.StatusRequestedRangeNotSatisfiable) return } w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", rng.StartOffset, rng.EndOffset, blob.Size)) @@ -241,12 +241,12 @@ func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsRespo if params.Range != nil { rng, err := httputil.ParseRange(*params.Range, blob.Size) if err != nil { - w.CodeMsg(http.StatusRequestedRangeNotSatisfiable, "") + w.String(fmt.Sprintf("get blob range fail %v", err), http.StatusRequestedRangeNotSatisfiable) return } w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", rng.StartOffset, rng.EndOffset, blob.Size)) w.Header().Set("Content-Length", fmt.Sprintf("%d", rng.EndOffset-rng.StartOffset+1)) - w.CodeMsg(http.StatusPartialContent, "") + w.Code(http.StatusPartialContent) } else { w.Header().Set("Content-Length", fmt.Sprint(blob.Size)) } diff --git a/controller/wip_ctl.go b/controller/wip_ctl.go new file mode 100644 index 00000000..d4f18484 --- /dev/null +++ b/controller/wip_ctl.go @@ -0,0 +1,220 @@ +package controller + +import ( + "bytes" + "context" + "encoding/hex" + "errors" + "fmt" + "net/http" + "time" + + "github.com/jiaozifs/jiaozifs/versionmgr" + + "github.com/google/uuid" + openapi_types "github.com/oapi-codegen/runtime/types" + + "github.com/jiaozifs/jiaozifs/utils" + + "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/models" + "go.uber.org/fx" +) + +type WipController struct { + fx.In + + Repo models.IRepo +} + +func (wipCtl WipController) CreateWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repository string, refID openapi_types.UUID, params api.CreateWipParams) { + user, err := wipCtl.Repo.UserRepo().Get(ctx, &models.GetUserParam{Name: utils.String(userName)}) + if err != nil { + w.Error(err) + return + } + + repo, err := wipCtl.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ + CreateID: user.ID, + Name: utils.String(repository), + }) + if err != nil { + w.Error(err) + return + } + + baseCommitID, err := hex.DecodeString(params.BaseCommitID) + if err != nil { + w.Error(err) + return + } + + wip := &models.WorkingInProcess{ + CurrentTree: baseCommitID, + BaseTree: baseCommitID, + RepositoryID: repo.ID, + RefID: refID, + State: 0, + Name: params.Name, + CreateID: user.ID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + wip, err = wipCtl.Repo.WipRepo().Insert(ctx, wip) + if err != nil { + w.Error(err) + return + } + w.JSON(wip, http.StatusCreated) +} + +func (wipCtl WipController) GetWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repositoryName string, refID openapi_types.UUID) { + user, err := wipCtl.Repo.UserRepo().Get(ctx, &models.GetUserParam{Name: utils.String(userName)}) + if err != nil { + w.Error(err) + return + } + + repository, err := wipCtl.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{Name: utils.String(repositoryName)}) + if err != nil { + w.Error(err) + return + } + + wip, err := wipCtl.Repo.WipRepo().Get(ctx, &models.GetWipParam{ + RefID: refID, + CreateID: user.ID, + RepositoryID: repository.ID, + }) + if err != nil { + if errors.Is(err, models.ErrNotFound) { + w.NotFound() + return + } + w.Error(err) + return + } + + w.JSON(wip) +} + +func (wipCtl WipController) ListWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repositoryName string) { + user, err := wipCtl.Repo.UserRepo().Get(ctx, &models.GetUserParam{Name: utils.String(userName)}) + if err != nil { + w.Error(err) + return + } + + repository, err := wipCtl.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{Name: utils.String(repositoryName)}) + if err != nil { + w.Error(err) + return + } + + wips, err := wipCtl.Repo.WipRepo().List(ctx, &models.ListWipParam{ + CreateID: user.ID, + RepositoryID: repository.ID, + }) + if err != nil { + w.Error(err) + return + } + + w.JSON(wips) +} + +func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repositoryName string, refID openapi_types.UUID, params api.CommitWipParams) { + user, err := wipCtl.Repo.UserRepo().Get(ctx, &models.GetUserParam{Name: utils.String(userName)}) + if err != nil { + w.Error(err) + return + } + + repository, err := wipCtl.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{Name: utils.String(repositoryName)}) + if err != nil { + w.Error(err) + return + } + + ref, err := wipCtl.Repo.RefRepo().Get(ctx, &models.GetRefParams{ID: refID}) + if err != nil { + w.Error(err) + return + } + + commit, err := wipCtl.Repo.ObjectRepo().Commit(ctx, ref.CommitHash) + if err != nil { + w.Error(err) + return + } + + wip, err := wipCtl.Repo.WipRepo().Get(ctx, &models.GetWipParam{ + RefID: refID, + CreateID: user.ID, + RepositoryID: repository.ID, + }) + if err != nil { + w.Error(err) + return + } + + if !bytes.Equal(commit.TreeHash, wip.BaseTree) { + w.Error(fmt.Errorf("base commit not equal with branch, please update wip")) + return + } + var msg string + if params.Msg != nil { + msg = *params.Msg + } + + //add commit + err = wipCtl.Repo.Transaction(ctx, func(repo models.IRepo) error { + commitOp := versionmgr.NewCommitOp(repo, commit) + commit, err := commitOp.AddCommit(ctx, user, wip.ID, msg) + if err != nil { + return err + } + + wip.BaseTree = commit.Commit().TreeHash //set for response + err = repo.WipRepo().UpdateBaseHash(ctx, wip.ID, commit.Commit().TreeHash) + if err != nil { + return err + } + + return repo.RefRepo().UpdateCommitHash(ctx, refID, commit.Commit().Hash) + }) + if err != nil { + w.Error(err) + return + } + + w.JSON(wip) +} + +// DeleteWip delete a active working in process +func (wipCtl WipController) DeleteWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repositoryName string, refID openapi_types.UUID) { + user, err := wipCtl.Repo.UserRepo().Get(ctx, &models.GetUserParam{Name: utils.String(userName)}) + if err != nil { + w.Error(err) + return + } + + repository, err := wipCtl.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{Name: utils.String(repositoryName)}) + if err != nil { + w.Error(err) + return + } + + err = wipCtl.Repo.WipRepo().Delete(ctx, &models.DeleteWipParam{ + ID: uuid.UUID{}, + CreateID: user.ID, + RepositoryID: repository.ID, + RefID: refID, + }) + if err != nil { + w.Error(err) + return + } + + w.OK() +} diff --git a/go.mod b/go.mod index 5d37f56a..8e8a7d49 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/aws/smithy-go v1.18.1 github.com/benburkert/dns v0.0.0-20190225204957-d356cf78cdfc github.com/brianvoe/gofakeit/v6 v6.25.0 + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/deepmap/oapi-codegen/v2 v2.0.1-0.20231120160225-add3126ee845 github.com/fergusstrange/embedded-postgres v1.25.0 @@ -50,6 +51,7 @@ require ( github.com/uptrace/bun/driver/pgdriver v1.1.16 github.com/uptrace/bun/extra/bundebug v1.1.16 go.uber.org/fx v1.20.1 + go.uber.org/mock v0.3.0 golang.org/x/crypto v0.16.0 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 golang.org/x/oauth2 v0.13.0 diff --git a/go.sum b/go.sum index fe84b7a0..fb3bc59e 100644 --- a/go.sum +++ b/go.sum @@ -121,6 +121,7 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -491,6 +492,8 @@ go.uber.org/fx v1.20.1 h1:zVwVQGS8zYvhh9Xxcu4w1M6ESyeMzebzj2NbSayZ4Mk= go.uber.org/fx v1.20.1/go.mod h1:iSYNbHf2y55acNCwCXKx7LbWb5WG1Bnue5RDXz1OREg= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= +go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= +go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= diff --git a/models/error.go b/models/error.go new file mode 100644 index 00000000..5bc62f8e --- /dev/null +++ b/models/error.go @@ -0,0 +1,7 @@ +package models + +import ( + "database/sql" +) + +var ErrNotFound = sql.ErrNoRows diff --git a/models/ref.go b/models/ref.go index da81f36b..0f1d7328 100644 --- a/models/ref.go +++ b/models/ref.go @@ -73,7 +73,7 @@ func (r RefRepo) Get(ctx context.Context, params *GetRefParams) (*Ref, error) { query = query.Where("name = ?", *params.Name) } - return repo, query.Scan(ctx, repo) + return repo, query.Limit(1).Scan(ctx, repo) } func (r RefRepo) UpdateCommitHash(ctx context.Context, id uuid.UUID, commitHash hash.Hash) error { diff --git a/models/repository.go b/models/repository.go index 7f15f565..f6369f43 100644 --- a/models/repository.go +++ b/models/repository.go @@ -65,5 +65,5 @@ func (r *RepositoryRepo) Get(ctx context.Context, params *GetRepoParams) (*Repos query = query.Where("name = ?", *params.Name) } - return repo, query.Scan(ctx, repo) + return repo, query.Limit(1).Scan(ctx, repo) } diff --git a/models/wip.go b/models/wip.go index d3836d33..a57df21c 100644 --- a/models/wip.go +++ b/models/wip.go @@ -19,10 +19,11 @@ const ( type WorkingInProcess struct { bun.BaseModel `bun:"table:wip"` ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` + Name string `bun:"name,notnull"` CurrentTree hash.Hash `bun:"current_tree,type:bytea,notnull"` - ParentTree hash.Hash `bun:"parent_tree,type:bytea,notnull"` - RefID uuid.UUID `bun:"ref_id,type:uuid,notnull"` + BaseTree hash.Hash `bun:"base_tree,type:bytea,notnull"` RepositoryID uuid.UUID `bun:"repository_id,type:uuid,notnull"` + RefID uuid.UUID `bun:"ref_id,type:uuid,notnull"` State WipState `bun:"state"` CreateID uuid.UUID `bun:"create_id,type:uuid,notnull"` CreatedAt time.Time `bun:"created_at"` @@ -33,12 +34,28 @@ type GetWipParam struct { ID uuid.UUID CreateID uuid.UUID RepositoryID uuid.UUID + RefID uuid.UUID } +type ListWipParam struct { + CreateID uuid.UUID + RepositoryID uuid.UUID + RefID uuid.UUID +} + +type DeleteWipParam struct { + ID uuid.UUID + CreateID uuid.UUID + RepositoryID uuid.UUID + RefID uuid.UUID +} type IWipRepo interface { Insert(ctx context.Context, repo *WorkingInProcess) (*WorkingInProcess, error) Get(ctx context.Context, params *GetWipParam) (*WorkingInProcess, error) + List(ctx context.Context, params *ListWipParam) ([]*WorkingInProcess, error) + Delete(ctx context.Context, params *DeleteWipParam) error UpdateCurrentHash(ctx context.Context, id uuid.UUID, newTreeHash hash.Hash) error + UpdateBaseHash(ctx context.Context, id uuid.UUID, treeHash hash.Hash) error UpdateState(ctx context.Context, id uuid.UUID, state WipState) error } @@ -60,6 +77,7 @@ func (s *WipRepo) Insert(ctx context.Context, repo *WorkingInProcess) (*WorkingI return repo, nil } +// Get wip by a group of conditions func (s *WipRepo) Get(ctx context.Context, params *GetWipParam) (*WorkingInProcess, error) { repo := &WorkingInProcess{} query := s.db.NewSelect().Model(repo) @@ -75,9 +93,34 @@ func (s *WipRepo) Get(ctx context.Context, params *GetWipParam) (*WorkingInProce if uuid.Nil != params.RepositoryID { query = query.Where("repository_id = ?", params.RepositoryID) } - return repo, query.Scan(ctx, repo) + + if uuid.Nil != params.RefID { + query = query.Where("ref_id = ?", params.RefID) + } + + return repo, query.Limit(1).Scan(ctx, repo) +} + +func (s *WipRepo) List(ctx context.Context, params *ListWipParam) ([]*WorkingInProcess, error) { + var resp []*WorkingInProcess + query := s.db.NewSelect().Model((*WorkingInProcess)(nil)) + + if uuid.Nil != params.CreateID { + query = query.Where("create_id = ?", params.CreateID) + } + + if uuid.Nil != params.RepositoryID { + query = query.Where("repository_id = ?", params.RepositoryID) + } + + if uuid.Nil != params.RefID { + query = query.Where("ref_id = ?", params.RefID) + } + + return resp, query.Scan(ctx, &resp) } +// UpdateCurrentHash update current hash func (s *WipRepo) UpdateCurrentHash(ctx context.Context, id uuid.UUID, newTreeHash hash.Hash) error { wip := &WorkingInProcess{ CurrentTree: newTreeHash, @@ -88,6 +131,18 @@ func (s *WipRepo) UpdateCurrentHash(ctx context.Context, id uuid.UUID, newTreeHa return err } +// UpdateBaseHash update base hash +func (s *WipRepo) UpdateBaseHash(ctx context.Context, id uuid.UUID, treeHash hash.Hash) error { + wip := &WorkingInProcess{ + BaseTree: treeHash, + } + _, err := s.db.NewUpdate().Model(wip).OmitZero().Column("base_tree"). + Where("id = ?", id). + Exec(ctx) + return err +} + +// UpdateState update wip state func (s *WipRepo) UpdateState(ctx context.Context, id uuid.UUID, state WipState) error { wip := &WorkingInProcess{ State: state, @@ -98,3 +153,26 @@ func (s *WipRepo) UpdateState(ctx context.Context, id uuid.UUID, state WipState) Exec(ctx) return err } + +// Delete remove wip in table by id +func (s *WipRepo) Delete(ctx context.Context, params *DeleteWipParam) error { + query := s.db.NewDelete().Model((*WorkingInProcess)(nil)) + + if uuid.Nil != params.CreateID { + query = query.Where("create_id = ?", params.CreateID) + } + + if uuid.Nil != params.RepositoryID { + query = query.Where("repository_id = ?", params.RepositoryID) + } + + if uuid.Nil != params.RefID { + query = query.Where("ref_id = ?", params.RefID) + } + + if uuid.Nil != params.ID { + query = query.Where("id = ?", params.ID) + } + _, err := query.Exec(ctx) + return err +} diff --git a/models/wip_test.go b/models/wip_test.go index 81bebf9a..c82c6777 100644 --- a/models/wip_test.go +++ b/models/wip_test.go @@ -27,9 +27,13 @@ func TestWipRepo(t *testing.T) { require.NoError(t, err) require.NotEqual(t, uuid.Nil, newWipModel.ID) - user, err := repo.Get(ctx, &models.GetWipParam{ID: newWipModel.ID}) + user, err := repo.Get(ctx, &models.GetWipParam{ + ID: newWipModel.ID, + CreateID: newWipModel.CreateID, + RepositoryID: newWipModel.RepositoryID, + RefID: newWipModel.RefID, + }) require.NoError(t, err) - require.True(t, cmp.Equal(newWipModel, user, dbTimeCmpOpt)) err = repo.UpdateCurrentHash(ctx, newWipModel.ID, hash.Hash("mock hash")) @@ -38,9 +42,45 @@ func TestWipRepo(t *testing.T) { require.NoError(t, err) require.Equal(t, "mock hash", string(updatedUser.CurrentTree)) + err = repo.UpdateBaseHash(ctx, newWipModel.ID, hash.Hash("mock base hash")) + require.NoError(t, err) + updatedUser, err = repo.Get(ctx, &models.GetWipParam{ID: newWipModel.ID}) + require.NoError(t, err) + require.Equal(t, "mock base hash", string(updatedUser.BaseTree)) + err = repo.UpdateState(ctx, newWipModel.ID, models.Completed) require.NoError(t, err) updatedUser, err = repo.Get(ctx, &models.GetWipParam{ID: newWipModel.ID}) require.NoError(t, err) require.Equal(t, models.Completed, updatedUser.State) + + t.Run("list", func(t *testing.T) { + secWipModel := &models.WorkingInProcess{} + require.NoError(t, gofakeit.Struct(secWipModel)) + secWipModel.CreateID = newWipModel.CreateID + secWipModel.RepositoryID = newWipModel.RepositoryID + secWipModel.RefID = newWipModel.RefID + secNewWipModel, err := repo.Insert(ctx, secWipModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, secNewWipModel.ID) + + list, err := repo.List(ctx, &models.ListWipParam{ + CreateID: secNewWipModel.CreateID, + RepositoryID: secNewWipModel.RepositoryID, + RefID: secWipModel.RefID, + }) + require.NoError(t, err) + require.Len(t, list, 2) + + err = repo.Delete(ctx, &models.DeleteWipParam{ + ID: secWipModel.ID, + CreateID: secWipModel.CreateID, + RepositoryID: secWipModel.RepositoryID, + RefID: secWipModel.RefID, + }) + require.NoError(t, err) + + _, err = repo.Get(ctx, &models.GetWipParam{ID: secWipModel.ID}) + require.ErrorIs(t, err, models.ErrNotFound) + }) } diff --git a/versionmgr/changes.go b/versionmgr/changes.go index f6bcc545..4a2e74f7 100644 --- a/versionmgr/changes.go +++ b/versionmgr/changes.go @@ -90,7 +90,7 @@ func (c *Changes) Changes() []IChange { return c.changes } -// Next get next element in array +// Next get element in array func (c *Changes) Next() (IChange, error) { if c.idx < len(c.changes)-1 { c.idx++ @@ -99,11 +99,25 @@ func (c *Changes) Next() (IChange, error) { return nil, io.EOF } -// Has checke whether all element was consumed +// Has check whether all element was consumed func (c *Changes) Has() bool { return c.idx < len(c.changes)-1 } +func (c *Changes) ForEach(fn func(IChange) error) error { + for _, change := range c.changes { + err := fn(change) + if err == nil { + continue + } + if errors.Is(err, ErrStop) { + return nil + } + return err + } + return nil +} + // Back a element in array func (c *Changes) Back() { if c.idx > -1 { diff --git a/versionmgr/changes_test.go b/versionmgr/changes_test.go index 5c29bcf8..d26e4b60 100644 --- a/versionmgr/changes_test.go +++ b/versionmgr/changes_test.go @@ -1,6 +1,7 @@ package versionmgr import ( + "fmt" "path/filepath" "strconv" "strings" @@ -279,3 +280,49 @@ func TestNewChangesMergeIter(t *testing.T) { require.Equal(t, "h3", string(finalChjange[1].From().Hash())) }) } + +func TestChanges_ForEach(t *testing.T) { + changeData1 := ` +1|c.txt|h3 +1|a.txt|h1 +1|b.txt|h2 +` + changeSet, err := makeMockChange(changeData1) + require.NoError(t, err) + + t.Run("simple", func(t *testing.T) { + var path []string + err := changeSet.ForEach(func(change IChange) error { + path = append(path, change.Path()) + return nil + }) + require.NoError(t, err) + require.Equal(t, []string{"a.txt", "b.txt", "c.txt"}, path) + }) + + t.Run("check stop", func(t *testing.T) { + var path []string + err := changeSet.ForEach(func(change IChange) error { + path = append(path, change.Path()) + if change.Path() == "b.txt" { + return ErrStop + } + return nil + }) + require.NoError(t, err) + require.Equal(t, []string{"a.txt", "b.txt"}, path) + }) + t.Run("error check", func(t *testing.T) { + var path []string + var stopErr = fmt.Errorf("stop at b,txt") + err := changeSet.ForEach(func(change IChange) error { + path = append(path, change.Path()) + if change.Path() == "b.txt" { + return stopErr + } + return nil + }) + require.ErrorIs(t, err, stopErr) + require.Equal(t, []string{"a.txt", "b.txt"}, path) + }) +} diff --git a/versionmgr/commit_test.go b/versionmgr/commit_test.go index 591f9218..ec308343 100644 --- a/versionmgr/commit_test.go +++ b/versionmgr/commit_test.go @@ -368,7 +368,7 @@ func makeRef(ctx context.Context, refRepo models.IRefRepo, name string, repoID u func makeWip(ctx context.Context, wipRepo models.IWipRepo, repoID, refID uuid.UUID, parentHash, curHash hash.Hash) (*models.WorkingInProcess, error) { wip := &models.WorkingInProcess{ CurrentTree: curHash, - ParentTree: parentHash, + BaseTree: parentHash, RefID: refID, RepositoryID: repoID, CreateID: uuid.UUID{}, From 2c0e3f3e95a818d25dbde74ceb83169fb4ab80dd Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Tue, 12 Dec 2023 21:14:14 +0800 Subject: [PATCH 064/210] refrator: change get/update/delete params initialize --- api/api_impl/impl.go | 1 + api/custom_response.go | 18 + api/custom_response_test.go | 31 + api/jiaozifs.gen.go | 1116 ++++++++++++++++++++++++++--- api/swagger.yml | 163 ++++- api/tmpls/chi/chi-interface.tmpl | 8 +- api/tmpls/chi/chi-middleware.tmpl | 13 +- auth/basic_auth.go | 8 +- controller/commit_ctl.go | 5 - controller/object_ctl.go | 24 +- controller/repository_ctl.go | 152 ++++ controller/user_ctl.go | 25 +- controller/wip_ctl.go | 59 +- models/object.go | 9 + models/object_test.go | 4 +- models/ref.go | 46 +- models/ref_test.go | 13 +- models/repo_test.go | 2 +- models/repository.go | 111 ++- models/repository_test.go | 34 +- models/user.go | 33 +- models/user_test.go | 8 +- models/wip.go | 178 +++-- models/wip_test.go | 93 ++- versionmgr/commit.go | 8 +- versionmgr/commit_test.go | 12 +- 26 files changed, 1865 insertions(+), 309 deletions(-) create mode 100644 controller/repository_ctl.go diff --git a/api/api_impl/impl.go b/api/api_impl/impl.go index 6ebebe18..19e048f6 100644 --- a/api/api_impl/impl.go +++ b/api/api_impl/impl.go @@ -16,4 +16,5 @@ type APIController struct { controller.UserController controller.WipController controller.CommitController + controller.RepositoryController } diff --git a/api/custom_response.go b/api/custom_response.go index f0c20860..2b85a0b6 100644 --- a/api/custom_response.go +++ b/api/custom_response.go @@ -2,7 +2,10 @@ package api import ( "encoding/json" + "errors" "net/http" + + "github.com/jiaozifs/jiaozifs/models" ) type JiaozifsResponse struct { @@ -36,8 +39,23 @@ func (response *JiaozifsResponse) NotFound() { response.WriteHeader(http.StatusNotFound) } +// Unauthorized response with 401 +func (response *JiaozifsResponse) Unauthorized() { + response.WriteHeader(http.StatusUnauthorized) +} + +func (response *JiaozifsResponse) BadRequest(msg string) { + response.WriteHeader(http.StatusBadRequest) + _, _ = response.Write([]byte(msg)) +} + // Error response with 500 and error message func (response *JiaozifsResponse) Error(err error) { + if errors.Is(err, models.ErrNotFound) { + response.NotFound() + return + } + response.WriteHeader(http.StatusInternalServerError) _, _ = response.Write([]byte(err.Error())) } diff --git a/api/custom_response_test.go b/api/custom_response_test.go index 3b9572f4..e5931a49 100644 --- a/api/custom_response_test.go +++ b/api/custom_response_test.go @@ -5,6 +5,8 @@ import ( "net/http" "testing" + "github.com/jiaozifs/jiaozifs/models" + "go.uber.org/mock/gomock" ) @@ -18,6 +20,15 @@ func TestJiaozifsResponse(t *testing.T) { jzResp.NotFound() }) + t.Run("not found", func(t *testing.T) { + ctrl := gomock.NewController(t) + resp := NewMockResponseWriter(ctrl) + jzResp := JiaozifsResponse{resp} + + resp.EXPECT().WriteHeader(http.StatusUnauthorized) + jzResp.Unauthorized() + }) + t.Run("ok", func(t *testing.T) { ctrl := gomock.NewController(t) resp := NewMockResponseWriter(ctrl) @@ -26,6 +37,17 @@ func TestJiaozifsResponse(t *testing.T) { resp.EXPECT().WriteHeader(http.StatusOK) jzResp.OK() }) + + t.Run("bad request", func(t *testing.T) { + ctrl := gomock.NewController(t) + resp := NewMockResponseWriter(ctrl) + jzResp := JiaozifsResponse{resp} + + resp.EXPECT().WriteHeader(http.StatusBadRequest) + resp.EXPECT().Write([]byte("bad request")) + jzResp.BadRequest("bad request") + }) + t.Run("code", func(t *testing.T) { ctrl := gomock.NewController(t) resp := NewMockResponseWriter(ctrl) @@ -44,6 +66,15 @@ func TestJiaozifsResponse(t *testing.T) { jzResp.Error(fmt.Errorf("mock")) }) + t.Run("error not found", func(t *testing.T) { + ctrl := gomock.NewController(t) + resp := NewMockResponseWriter(ctrl) + jzResp := JiaozifsResponse{resp} + + resp.EXPECT().WriteHeader(http.StatusNotFound) + jzResp.Error(fmt.Errorf("mock %w", models.ErrNotFound)) + }) + t.Run("string", func(t *testing.T) { ctrl := gomock.NewController(t) resp := NewMockResponseWriter(ctrl) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 2dd56a48..9ab5b129 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -63,6 +63,17 @@ type ObjectStats struct { // ObjectUserMetadata defines model for ObjectUserMetadata. type ObjectUserMetadata map[string]string +// Repository defines model for Repository. +type Repository struct { + CreatedAt *time.Time `json:"CreatedAt,omitempty"` + CreatorID *openapi_types.UUID `json:"CreatorID,omitempty"` + Description *string `json:"Description,omitempty"` + Head *string `json:"Head,omitempty"` + ID *openapi_types.UUID `json:"ID,omitempty"` + Name *string `json:"Name,omitempty"` + UpdatedAt *time.Time `json:"UpdatedAt,omitempty"` +} + // TreeEntry defines model for TreeEntry. type TreeEntry struct { Hash *string `json:"Hash,omitempty"` @@ -70,6 +81,11 @@ type TreeEntry struct { Name *string `json:"Name,omitempty"` } +// UpdateRepository defines model for UpdateRepository. +type UpdateRepository struct { + Description *string `json:"Description,omitempty"` +} + // UserInfo defines model for UserInfo. type UserInfo struct { CreatedAt *time.Time `json:"createdAt,omitempty"` @@ -101,8 +117,8 @@ type VersionResult struct { // Wip defines model for Wip. type Wip struct { BaseTree *string `json:"BaseTree,omitempty"` - CreateID *openapi_types.UUID `json:"CreateID,omitempty"` CreatedAt *time.Time `json:"CreatedAt,omitempty"` + CreatorID *openapi_types.UUID `json:"CreatorID,omitempty"` CurrentTree *string `json:"CurrentTree,omitempty"` ID *openapi_types.UUID `json:"ID,omitempty"` Name *string `json:"Name,omitempty"` @@ -117,6 +133,15 @@ type LoginJSONBody struct { Username string `json:"username"` } +// CreateRepositoryParams defines parameters for CreateRepository. +type CreateRepositoryParams struct { + // Name repository name + Name string `form:"name" json:"name"` + + // Description repository description + Description *string `form:"description,omitempty" json:"description,omitempty"` +} + // GetCommitDiffParams defines parameters for GetCommitDiff. type GetCommitDiffParams struct { // Path specific path, if not specific return entries in root @@ -208,6 +233,9 @@ type LoginJSONRequestBody LoginJSONBody // RegisterJSONRequestBody defines body for Register for application/json ContentType. type RegisterJSONRequestBody = UserRegisterInfo +// UpdateRepositoryJSONRequestBody defines body for UpdateRepository for application/json ContentType. +type UpdateRepositoryJSONRequestBody = UpdateRepository + // UploadObjectMultipartRequestBody defines body for UploadObject for multipart/form-data ContentType. type UploadObjectMultipartRequestBody UploadObjectMultipartBody @@ -300,6 +328,23 @@ type ClientInterface interface { // GetVersion request GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // ListRepository request + ListRepository(ctx context.Context, user string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateRepository request + CreateRepository(ctx context.Context, user string, params *CreateRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // DeleteRepository request + DeleteRepository(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetRepository request + GetRepository(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UpdateRepositoryWithBody request with any body + UpdateRepositoryWithBody(ctx context.Context, user string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + UpdateRepository(ctx context.Context, user string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetCommitDiff request GetCommitDiff(ctx context.Context, user string, repository string, baseCommit string, toCommit string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -406,6 +451,78 @@ func (c *Client) GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) return c.Client.Do(req) } +func (c *Client) ListRepository(ctx context.Context, user string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListRepositoryRequest(c.Server, user) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateRepository(ctx context.Context, user string, params *CreateRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateRepositoryRequest(c.Server, user, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) DeleteRepository(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteRepositoryRequest(c.Server, user, repository) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetRepository(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetRepositoryRequest(c.Server, user, repository) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateRepositoryWithBody(ctx context.Context, user string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateRepositoryRequestWithBody(c.Server, user, repository, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateRepository(ctx context.Context, user string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateRepositoryRequest(c.Server, user, repository, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) GetCommitDiff(ctx context.Context, user string, repository string, baseCommit string, toCommit string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewGetCommitDiffRequest(c.Server, user, repository, baseCommit, toCommit, params) if err != nil { @@ -632,29 +749,265 @@ func NewGetUserInfoRequest(server string) (*http.Request, error) { operationPath = "." + operationPath } - queryURL, err := serverURL.Parse(operationPath) + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetVersionRequest generates requests for GetVersion +func NewGetVersionRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/version") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewListRepositoryRequest generates requests for ListRepository +func NewListRepositoryRequest(server string, user string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/%s/repository/list", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewCreateRepositoryRequest generates requests for CreateRepository +func NewCreateRepositoryRequest(server string, user string, params *CreateRepositoryParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/%s/repository/new", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "name", runtime.ParamLocationQuery, params.Name); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + if params.Description != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "description", runtime.ParamLocationQuery, *params.Description); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("POST", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewDeleteRepositoryRequest generates requests for DeleteRepository +func NewDeleteRepositoryRequest(server string, user string, repository string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/%s/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetRepositoryRequest generates requests for GetRepository +func NewGetRepositoryRequest(server string, user string, repository string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/%s/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewUpdateRepositoryRequest calls the generic UpdateRepository builder with application/json body +func NewUpdateRepositoryRequest(server string, user string, repository string, body UpdateRepositoryJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewUpdateRepositoryRequestWithBody(server, user, repository, "application/json", bodyReader) +} + +// NewUpdateRepositoryRequestWithBody generates requests for UpdateRepository with any type of body +func NewUpdateRepositoryRequestWithBody(server string, user string, repository string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) if err != nil { return nil, err } - req, err := http.NewRequest("GET", queryURL.String(), nil) + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) if err != nil { return nil, err } - return req, nil -} - -// NewGetVersionRequest generates requests for GetVersion -func NewGetVersionRequest(server string) (*http.Request, error) { - var err error - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/version") + operationPath := fmt.Sprintf("/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -664,11 +1017,13 @@ func NewGetVersionRequest(server string) (*http.Request, error) { return nil, err } - req, err := http.NewRequest("GET", queryURL.String(), nil) + req, err := http.NewRequest("POST", queryURL.String(), body) if err != nil { return nil, err } + req.Header.Add("Content-Type", contentType) + return req, nil } @@ -1518,6 +1873,23 @@ type ClientWithResponsesInterface interface { // GetVersionWithResponse request GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) + // ListRepositoryWithResponse request + ListRepositoryWithResponse(ctx context.Context, user string, reqEditors ...RequestEditorFn) (*ListRepositoryResponse, error) + + // CreateRepositoryWithResponse request + CreateRepositoryWithResponse(ctx context.Context, user string, params *CreateRepositoryParams, reqEditors ...RequestEditorFn) (*CreateRepositoryResponse, error) + + // DeleteRepositoryWithResponse request + DeleteRepositoryWithResponse(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*DeleteRepositoryResponse, error) + + // GetRepositoryWithResponse request + GetRepositoryWithResponse(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*GetRepositoryResponse, error) + + // UpdateRepositoryWithBodyWithResponse request with any body + UpdateRepositoryWithBodyWithResponse(ctx context.Context, user string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) + + UpdateRepositoryWithResponse(ctx context.Context, user string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) + // GetCommitDiffWithResponse request GetCommitDiffWithResponse(ctx context.Context, user string, repository string, baseCommit string, toCommit string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*GetCommitDiffResponse, error) @@ -1639,6 +2011,114 @@ func (r GetVersionResponse) StatusCode() int { return 0 } +type ListRepositoryResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]Repository +} + +// Status returns HTTPResponse.Status +func (r ListRepositoryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ListRepositoryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type CreateRepositoryResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *[]Repository +} + +// Status returns HTTPResponse.Status +func (r CreateRepositoryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateRepositoryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type DeleteRepositoryResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r DeleteRepositoryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DeleteRepositoryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetRepositoryResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Repository +} + +// Status returns HTTPResponse.Status +func (r GetRepositoryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetRepositoryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type UpdateRepositoryResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r UpdateRepositoryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UpdateRepositoryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type GetCommitDiffResponse struct { Body []byte HTTPResponse *http.Response @@ -1929,6 +2409,59 @@ func (c *ClientWithResponses) GetVersionWithResponse(ctx context.Context, reqEdi return ParseGetVersionResponse(rsp) } +// ListRepositoryWithResponse request returning *ListRepositoryResponse +func (c *ClientWithResponses) ListRepositoryWithResponse(ctx context.Context, user string, reqEditors ...RequestEditorFn) (*ListRepositoryResponse, error) { + rsp, err := c.ListRepository(ctx, user, reqEditors...) + if err != nil { + return nil, err + } + return ParseListRepositoryResponse(rsp) +} + +// CreateRepositoryWithResponse request returning *CreateRepositoryResponse +func (c *ClientWithResponses) CreateRepositoryWithResponse(ctx context.Context, user string, params *CreateRepositoryParams, reqEditors ...RequestEditorFn) (*CreateRepositoryResponse, error) { + rsp, err := c.CreateRepository(ctx, user, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateRepositoryResponse(rsp) +} + +// DeleteRepositoryWithResponse request returning *DeleteRepositoryResponse +func (c *ClientWithResponses) DeleteRepositoryWithResponse(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*DeleteRepositoryResponse, error) { + rsp, err := c.DeleteRepository(ctx, user, repository, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteRepositoryResponse(rsp) +} + +// GetRepositoryWithResponse request returning *GetRepositoryResponse +func (c *ClientWithResponses) GetRepositoryWithResponse(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*GetRepositoryResponse, error) { + rsp, err := c.GetRepository(ctx, user, repository, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetRepositoryResponse(rsp) +} + +// UpdateRepositoryWithBodyWithResponse request with arbitrary body returning *UpdateRepositoryResponse +func (c *ClientWithResponses) UpdateRepositoryWithBodyWithResponse(ctx context.Context, user string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) { + rsp, err := c.UpdateRepositoryWithBody(ctx, user, repository, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateRepositoryResponse(rsp) +} + +func (c *ClientWithResponses) UpdateRepositoryWithResponse(ctx context.Context, user string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) { + rsp, err := c.UpdateRepository(ctx, user, repository, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateRepositoryResponse(rsp) +} + // GetCommitDiffWithResponse request returning *GetCommitDiffResponse func (c *ClientWithResponses) GetCommitDiffWithResponse(ctx context.Context, user string, repository string, baseCommit string, toCommit string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*GetCommitDiffResponse, error) { rsp, err := c.GetCommitDiff(ctx, user, repository, baseCommit, toCommit, params, reqEditors...) @@ -2025,44 +2558,164 @@ func (c *ClientWithResponses) CreateWipWithResponse(ctx context.Context, user st if err != nil { return nil, err } - return ParseCreateWipResponse(rsp) + return ParseCreateWipResponse(rsp) +} + +// ParseLoginResponse parses an HTTP response from a LoginWithResponse call +func ParseLoginResponse(rsp *http.Response) (*LoginResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &LoginResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest AuthenticationToken + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseRegisterResponse parses an HTTP response from a RegisterWithResponse call +func ParseRegisterResponse(rsp *http.Response) (*RegisterResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &RegisterResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetUserInfoResponse parses an HTTP response from a GetUserInfoWithResponse call +func ParseGetUserInfoResponse(rsp *http.Response) (*GetUserInfoResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetUserInfoResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest UserInfo + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseGetVersionResponse parses an HTTP response from a GetVersionWithResponse call +func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetVersionResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest VersionResult + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseListRepositoryResponse parses an HTTP response from a ListRepositoryWithResponse call +func ParseListRepositoryResponse(rsp *http.Response) (*ListRepositoryResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListRepositoryResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []Repository + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil } -// ParseLoginResponse parses an HTTP response from a LoginWithResponse call -func ParseLoginResponse(rsp *http.Response) (*LoginResponse, error) { +// ParseCreateRepositoryResponse parses an HTTP response from a CreateRepositoryWithResponse call +func ParseCreateRepositoryResponse(rsp *http.Response) (*CreateRepositoryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &LoginResponse{ + response := &CreateRepositoryResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest AuthenticationToken + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest []Repository if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON200 = &dest + response.JSON201 = &dest } return response, nil } -// ParseRegisterResponse parses an HTTP response from a RegisterWithResponse call -func ParseRegisterResponse(rsp *http.Response) (*RegisterResponse, error) { +// ParseDeleteRepositoryResponse parses an HTTP response from a DeleteRepositoryWithResponse call +func ParseDeleteRepositoryResponse(rsp *http.Response) (*DeleteRepositoryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &RegisterResponse{ + response := &DeleteRepositoryResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -2070,22 +2723,22 @@ func ParseRegisterResponse(rsp *http.Response) (*RegisterResponse, error) { return response, nil } -// ParseGetUserInfoResponse parses an HTTP response from a GetUserInfoWithResponse call -func ParseGetUserInfoResponse(rsp *http.Response) (*GetUserInfoResponse, error) { +// ParseGetRepositoryResponse parses an HTTP response from a GetRepositoryWithResponse call +func ParseGetRepositoryResponse(rsp *http.Response) (*GetRepositoryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetUserInfoResponse{ + response := &GetRepositoryResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest UserInfo + var dest Repository if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -2096,29 +2749,19 @@ func ParseGetUserInfoResponse(rsp *http.Response) (*GetUserInfoResponse, error) return response, nil } -// ParseGetVersionResponse parses an HTTP response from a GetVersionWithResponse call -func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) { +// ParseUpdateRepositoryResponse parses an HTTP response from a UpdateRepositoryWithResponse call +func ParseUpdateRepositoryResponse(rsp *http.Response) (*UpdateRepositoryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetVersionResponse{ + response := &UpdateRepositoryResponse{ Body: bodyBytes, HTTPResponse: rsp, } - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest VersionResult - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - } - return response, nil } @@ -2372,16 +3015,31 @@ func ParseCreateWipResponse(rsp *http.Response) (*CreateWipResponse, error) { type ServerInterface interface { // perform a login // (POST /auth/login) - Login(ctx context.Context, w *JiaozifsResponse, r *http.Request) + Login(ctx context.Context, w *JiaozifsResponse, r *http.Request, body LoginJSONRequestBody) // perform user registration // (POST /auth/register) - Register(ctx context.Context, w *JiaozifsResponse, r *http.Request) + Register(ctx context.Context, w *JiaozifsResponse, r *http.Request, body RegisterJSONRequestBody) // get information of the currently logged-in user // (GET /auth/user) GetUserInfo(ctx context.Context, w *JiaozifsResponse, r *http.Request) // return program and runtime version // (GET /version) GetVersion(ctx context.Context, w *JiaozifsResponse, r *http.Request) + // list repository + // (GET /{user}/repository/list) + ListRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string) + // create repository + // (POST /{user}/repository/new) + CreateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, params CreateRepositoryParams) + // delete repository + // (DELETE /{user}/{repository}) + DeleteRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string) + // get repository + // (GET /{user}/{repository}) + GetRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string) + // update repository + // (POST /{user}/{repository}) + UpdateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateRepositoryJSONRequestBody, user string, repository string) // get commit differences // (GET /{user}/{repository}/commit/diff/{baseCommit}/{toCommit}) GetCommitDiff(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, baseCommit string, toCommit string, params GetCommitDiffParams) @@ -2423,13 +3081,13 @@ type Unimplemented struct{} // perform a login // (POST /auth/login) -func (_ Unimplemented) Login(ctx context.Context, w *JiaozifsResponse, r *http.Request) { +func (_ Unimplemented) Login(ctx context.Context, w *JiaozifsResponse, r *http.Request, body LoginJSONRequestBody) { w.WriteHeader(http.StatusNotImplemented) } // perform user registration // (POST /auth/register) -func (_ Unimplemented) Register(ctx context.Context, w *JiaozifsResponse, r *http.Request) { +func (_ Unimplemented) Register(ctx context.Context, w *JiaozifsResponse, r *http.Request, body RegisterJSONRequestBody) { w.WriteHeader(http.StatusNotImplemented) } @@ -2445,6 +3103,36 @@ func (_ Unimplemented) GetVersion(ctx context.Context, w *JiaozifsResponse, r *h w.WriteHeader(http.StatusNotImplemented) } +// list repository +// (GET /{user}/repository/list) +func (_ Unimplemented) ListRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// create repository +// (POST /{user}/repository/new) +func (_ Unimplemented) CreateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, params CreateRepositoryParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// delete repository +// (DELETE /{user}/{repository}) +func (_ Unimplemented) DeleteRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get repository +// (GET /{user}/{repository}) +func (_ Unimplemented) GetRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// update repository +// (POST /{user}/{repository}) +func (_ Unimplemented) UpdateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateRepositoryJSONRequestBody, user string, repository string) { + w.WriteHeader(http.StatusNotImplemented) +} + // get commit differences // (GET /{user}/{repository}/commit/diff/{baseCommit}/{toCommit}) func (_ Unimplemented) GetCommitDiff(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, baseCommit string, toCommit string, params GetCommitDiffParams) { @@ -2523,8 +3211,18 @@ type MiddlewareFunc func(http.Handler) http.Handler func (siw *ServerInterfaceWrapper) Login(w http.ResponseWriter, r *http.Request) { ctx := r.Context() + // ------------- Body parse ------------- + var body LoginJSONRequestBody + parseBody := r.ContentLength != 0 + if parseBody { + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "Error unmarshalling body 'Login' as JSON", http.StatusBadRequest) + return + } + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.Login(r.Context(), &JiaozifsResponse{w}, r) + siw.Handler.Login(r.Context(), &JiaozifsResponse{w}, r, body) })) for _, middleware := range siw.HandlerMiddlewares { @@ -2538,8 +3236,18 @@ func (siw *ServerInterfaceWrapper) Login(w http.ResponseWriter, r *http.Request) func (siw *ServerInterfaceWrapper) Register(w http.ResponseWriter, r *http.Request) { ctx := r.Context() + // ------------- Body parse ------------- + var body RegisterJSONRequestBody + parseBody := r.ContentLength != 0 + if parseBody { + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "Error unmarshalling body 'Register' as JSON", http.StatusBadRequest) + return + } + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.Register(r.Context(), &JiaozifsResponse{w}, r) + siw.Handler.Register(r.Context(), &JiaozifsResponse{w}, r, body) })) for _, middleware := range siw.HandlerMiddlewares { @@ -2583,6 +3291,219 @@ func (siw *ServerInterfaceWrapper) GetVersion(w http.ResponseWriter, r *http.Req handler.ServeHTTP(w, r.WithContext(ctx)) } +// ListRepository operation middleware +func (siw *ServerInterfaceWrapper) ListRepository(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "user" ------------- + var user string + + err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListRepository(r.Context(), &JiaozifsResponse{w}, r, user) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// CreateRepository operation middleware +func (siw *ServerInterfaceWrapper) CreateRepository(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "user" ------------- + var user string + + err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params CreateRepositoryParams + + // ------------- Required query parameter "name" ------------- + + if paramValue := r.URL.Query().Get("name"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "name"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "name", r.URL.Query(), ¶ms.Name) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "name", Err: err}) + return + } + + // ------------- Optional query parameter "description" ------------- + + err = runtime.BindQueryParameter("form", true, false, "description", r.URL.Query(), ¶ms.Description) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "description", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CreateRepository(r.Context(), &JiaozifsResponse{w}, r, user, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// DeleteRepository operation middleware +func (siw *ServerInterfaceWrapper) DeleteRepository(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "user" ------------- + var user string + + err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteRepository(r.Context(), &JiaozifsResponse{w}, r, user, repository) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// GetRepository operation middleware +func (siw *ServerInterfaceWrapper) GetRepository(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "user" ------------- + var user string + + err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetRepository(r.Context(), &JiaozifsResponse{w}, r, user, repository) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// UpdateRepository operation middleware +func (siw *ServerInterfaceWrapper) UpdateRepository(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Body parse ------------- + var body UpdateRepositoryJSONRequestBody + parseBody := true + if parseBody { + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "Error unmarshalling body 'UpdateRepository' as JSON", http.StatusBadRequest) + return + } + } + + // ------------- Path parameter "user" ------------- + var user string + + err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UpdateRepository(r.Context(), &JiaozifsResponse{w}, r, body, user, repository) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // GetCommitDiff operation middleware func (siw *ServerInterfaceWrapper) GetCommitDiff(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -3491,6 +4412,21 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/version", wrapper.GetVersion) }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/{user}/repository/list", wrapper.ListRepository) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/{user}/repository/new", wrapper.CreateRepository) + }) + r.Group(func(r chi.Router) { + r.Delete(options.BaseURL+"/{user}/{repository}", wrapper.DeleteRepository) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/{user}/{repository}", wrapper.GetRepository) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/{user}/{repository}", wrapper.UpdateRepository) + }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/{user}/{repository}/commit/diff/{baseCommit}/{toCommit}", wrapper.GetCommitDiff) }) @@ -3531,49 +4467,53 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "AA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 850fc129..e053f172 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -37,6 +37,32 @@ components: in: cookie name: saml_auth_session schemas: + UpdateRepository: + type: object + properties: + Description: + type: string + Repository: + type: object + properties: + ID: + type: string + format: uuid + Name: + type: string + Head: + type: string + Description: + type: string + CreatorID: + type: string + format: uuid + CreatedAt: + type: string + format: date-time + UpdatedAt: + type: string + format: date-time Blob: type: object properties: @@ -141,7 +167,7 @@ components: State: type: integer format: int - CreateID: + CreatorID: type: string format: uuid CreatedAt: @@ -664,6 +690,7 @@ paths: description: Forbidden 502: description: internal server error + /{user}/{repository}/wip/commit/{refID}: parameters: - in: path @@ -841,6 +868,140 @@ paths: description: server internal error + /{user}/repository/new: + parameters: + - in: path + name: user + required: true + schema: + type: string + post: + tags: + - repo + operationId: createRepository + summary: create repository + parameters: + - in: query + name: name + description: repository name + required: true + schema: + type: string + - in: query + name: description + description: repository description + required: false + schema: + type: string + responses: + 201: + description: new repository + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Repository" + 400: + description: ValidationError + 401: + description: Unauthorized + 403: + description: Forbidden + + /{user}/{repository}: + parameters: + - in: path + name: user + required: true + schema: + type: string + - in: path + name: repository + required: true + schema: + type: string + get: + tags: + - repo + operationId: getRepository + summary: get repository + responses: + 200: + description: get repository + content: + application/json: + schema: + $ref: "#/components/schemas/Repository" + 400: + description: ValidationError + 401: + description: Unauthorized + 403: + description: Forbidden + delete: + tags: + - repo + operationId: deleteRepository + summary: delete repository + responses: + 200: + description: success to delete repository + 400: + description: ValidationError + 401: + description: Unauthorized + 403: + description: Forbidden + post: + tags: + - repo + operationId: updateRepository + summary: update repository + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/UpdateRepository" + responses: + 200: + description: success to update repository + 400: + description: ValidationError + 401: + description: Unauthorized + 403: + description: Forbidden + + /{user}/repository/list: + parameters: + - in: path + name: user + required: true + schema: + type: string + get: + tags: + - repo + operationId: listRepository + summary: list repository + responses: + 200: + description: list repository + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Repository" + 400: + description: ValidationError + 401: + description: Unauthorized + 403: + description: Forbidden + /auth/login: post: tags: diff --git a/api/tmpls/chi/chi-interface.tmpl b/api/tmpls/chi/chi-interface.tmpl index 11f1b0af..9e8b4d14 100644 --- a/api/tmpls/chi/chi-interface.tmpl +++ b/api/tmpls/chi/chi-interface.tmpl @@ -2,16 +2,18 @@ type ServerInterface interface { {{range .}}{{.SummaryAsComment }} // ({{.Method}} {{.Path}}) -{{.OperationId}}(ctx context.Context, w *JiaozifsResponse, r *http.Request{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) +{{.OperationId}}{{$opid := .OperationId}}(ctx context.Context, w *JiaozifsResponse, r *http.Request +{{- if .HasBody}}{{range .Bodies}}{{if and .Default (eq .NameTag "JSON")}}, body {{$opid}}{{.NameTag}}RequestBody {{end}}{{end}}{{end -}} +{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) {{end}} } // Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. type Unimplemented struct {} - {{range .}}{{.SummaryAsComment }} + {{range .}}{{.SummaryAsComment }}{{$opid := .OperationId}} // ({{.Method}} {{.Path}}) - func (_ Unimplemented) {{.OperationId}}(ctx context.Context, w *JiaozifsResponse, r *http.Request{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) { + func (_ Unimplemented) {{.OperationId}}(ctx context.Context, w *JiaozifsResponse, r *http.Request{{- if .HasBody}}{{range .Bodies}}{{if and .Default (eq .NameTag "JSON")}}, body {{$opid}}{{.NameTag}}RequestBody {{end}}{{end}}{{end -}}{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) { w.WriteHeader(http.StatusNotImplemented) } {{end}} diff --git a/api/tmpls/chi/chi-middleware.tmpl b/api/tmpls/chi/chi-middleware.tmpl index c869af00..10c7ed54 100644 --- a/api/tmpls/chi/chi-middleware.tmpl +++ b/api/tmpls/chi/chi-middleware.tmpl @@ -16,6 +16,17 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ var err error {{end}} + {{if .HasBody}}{{range .Bodies}}{{if and .Default (eq .NameTag "JSON")}}// ------------- Body parse ------------- + var body {{$opid}}{{.NameTag}}RequestBody + parseBody := {{if .Required}}true{{else}}r.ContentLength != 0{{end}} + if parseBody { + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "Error unmarshalling body '{{$opid}}' as JSON", http.StatusBadRequest) + return + } + } + {{end}}{{end}}{{end}} + {{range .PathParams}}// ------------- Path parameter "{{.ParamName}}" ------------- var {{$varName := .GoVariableName}}{{$varName}} {{.TypeDef}} @@ -174,7 +185,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ {{end}} handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.{{.OperationId}}(r.Context(), &JiaozifsResponse{w}, r{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}) + siw.Handler.{{.OperationId}}(r.Context(), &JiaozifsResponse{w}, r{{if .HasBody}}{{range .Bodies}}{{if and .Default (eq .NameTag "JSON")}}, body{{end}}{{end}}{{end}}{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}) })) {{if opts.Compatibility.ApplyChiMiddlewareFirstToLast}} diff --git a/auth/basic_auth.go b/auth/basic_auth.go index dbdca4cc..d0c9eba6 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -5,8 +5,6 @@ import ( "fmt" "time" - "github.com/jiaozifs/jiaozifs/utils" - "github.com/jiaozifs/jiaozifs/config" "github.com/golang-jwt/jwt" @@ -64,11 +62,11 @@ type Register struct { func (r *Register) Register(ctx context.Context, repo models.IUserRepo) error { // check username, email - count1, err := repo.Count(ctx, &models.CountUserParams{Name: utils.String(r.Username)}) + count1, err := repo.Count(ctx, models.NewCountUserParam().SetName(r.Username)) if err != nil { return err } - count2, err := repo.Count(ctx, &models.CountUserParams{Name: utils.String(r.Email)}) + count2, err := repo.Count(ctx, models.NewCountUserParam().SetName(r.Email)) if err != nil { return err } @@ -129,7 +127,7 @@ func (u *UserInfo) UserProfile(ctx context.Context, repo models.IUserRepo, confi username := claims["sub"].(string) // Get user by username - user, err := repo.Get(ctx, &models.GetUserParam{Name: utils.String(username)}) + user, err := repo.Get(ctx, models.NewGetUserParams().SetName(username)) if err != nil { return userInfo, err } diff --git a/controller/commit_ctl.go b/controller/commit_ctl.go index 8ffe9c43..ac86b65a 100644 --- a/controller/commit_ctl.go +++ b/controller/commit_ctl.go @@ -3,7 +3,6 @@ package controller import ( "context" "encoding/hex" - "errors" "net/http" "strings" @@ -48,10 +47,6 @@ func (commitCtl CommitController) GetEntriesInCommit(ctx context.Context, w *api } treeEntry, err := workTree.Ls(ctx, path) if err != nil { - if errors.Is(err, versionmgr.ErrPathNotFound) { - w.NotFound() - return - } w.Error(err) return } diff --git a/controller/object_ctl.go b/controller/object_ctl.go index 768f6793..2e524e4e 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -38,15 +38,15 @@ type ObjectController struct { } func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, userName string, repositoryName string, params api.DeleteObjectParams) { //nolint - user, err := oct.Repo.UserRepo().Get(ctx, &models.GetUserParam{Name: utils.String(userName)}) + user, err := oct.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) if err != nil { w.Error(err) return } repository, err := oct.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ - CreateID: user.ID, - Name: utils.String(repositoryName), + CreatorID: user.ID, + Name: utils.String(repositoryName), }) if err != nil { w.Error(err) @@ -79,7 +79,7 @@ func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsRes return } - err = oct.Repo.WipRepo().UpdateCurrentHash(ctx, params.WipID, workTree.Root().Hash()) + err = oct.Repo.WipRepo().UpdateByID(ctx, models.NewUpdateWipParams(params.WipID).SetCurrentTree(workTree.Root().Hash())) if err != nil { w.Error(err) return @@ -88,15 +88,15 @@ func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsRes } func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, userName string, repositoryName string, params api.GetObjectParams) { //nolint - user, err := oct.Repo.UserRepo().Get(ctx, &models.GetUserParam{Name: utils.String(userName)}) + user, err := oct.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) if err != nil { w.Error(err) return } repository, err := oct.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ - CreateID: user.ID, - Name: utils.String(repositoryName), + CreatorID: user.ID, + Name: utils.String(repositoryName), }) if err != nil { w.Error(err) @@ -171,15 +171,15 @@ func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsRespon } func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, userName string, repository string, params api.HeadObjectParams) { //nolint - user, err := oct.Repo.UserRepo().Get(ctx, &models.GetUserParam{Name: utils.String(userName)}) + user, err := oct.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) if err != nil { w.Error(err) return } repo, err := oct.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ - CreateID: user.ID, - Name: utils.String(repository), + CreatorID: user.ID, + Name: utils.String(repository), }) if err != nil { w.Error(err) @@ -300,7 +300,7 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes var response api.ObjectStats err = oct.Repo.Transaction(ctx, func(dRepo models.IRepo) error { - stash, err := dRepo.WipRepo().Get(ctx, &models.GetWipParam{ + stash, err := dRepo.WipRepo().Get(ctx, &models.GetWipParams{ ID: params.WipID, }) if err != nil { @@ -330,7 +330,7 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes ContentType: &contentType, Metadata: &api.ObjectUserMetadata{}, } - return dRepo.WipRepo().UpdateCurrentHash(ctx, stash.ID, workingTree.Root().Hash()) + return dRepo.WipRepo().UpdateByID(ctx, models.NewUpdateWipParams(stash.ID).SetCurrentTree(workingTree.Root().Hash())) }) if err != nil { diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go new file mode 100644 index 00000000..c7823e41 --- /dev/null +++ b/controller/repository_ctl.go @@ -0,0 +1,152 @@ +package controller + +import ( + "context" + "errors" + "net/http" + "regexp" + "time" + + "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/utils" + "go.uber.org/fx" +) + +var maxNameLength = 20 +var alphanumeric = regexp.MustCompile("^[a-zA-Z0-9_]*$") + +var RepoNameBlackList = []string{"repository"} + +func CheckRepositoryName(name string) error { + for _, blackName := range RepoNameBlackList { + if name == blackName { + return errors.New("repository name is black list") + } + } + + if !alphanumeric.MatchString(name) { + return errors.New("repository name must be combination of number and letter") + } + if len(name) > maxNameLength { + return errors.New("repository name is too long") + } + return nil +} + +type RepositoryController struct { + fx.In + + Repo models.IRepo +} + +func (repositoryCtl RepositoryController) ListRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string) { + user, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) + if err != nil { + w.Error(err) + return + } + + repositories, err := repositoryCtl.Repo.RepositoryRepo().List(ctx, &models.ListRepoParams{ + CreatorID: user.ID, + }) + if err != nil { + w.Error(err) + return + } + w.JSON(repositories) +} + +func (repositoryCtl RepositoryController) CreateRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, params api.CreateRepositoryParams) { + err := CheckRepositoryName(params.Name) + if err != nil { + w.BadRequest(err.Error()) + return + } + + user, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) + if err != nil { + w.Error(err) + return + } + + repository := &models.Repository{ + Name: params.Name, + Description: params.Description, + HEAD: "main", + CreatorID: user.ID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + repository, err = repositoryCtl.Repo.RepositoryRepo().Insert(ctx, repository) + if err != nil { + w.Error(err) + return + } + w.JSON(repository) +} + +func (repositoryCtl RepositoryController) DeleteRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repositoryName string) { + user, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) + if err != nil { + w.Error(err) + return + } + + repo, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ + CreatorID: user.ID, + Name: utils.String(repositoryName), + }) + if err != nil { + w.Error(err) + return + } + + err = repositoryCtl.Repo.RepositoryRepo().Delete(ctx, models.NewDeleteRepoParams().SetID(repo.ID)) + if err != nil { + w.Error(err) + return + } + w.OK() +} + +func (repositoryCtl RepositoryController) GetRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repositoryName string) { + user, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) + if err != nil { + w.Error(err) + return + } + repo, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ + CreatorID: user.ID, + Name: utils.String(repositoryName), + }) + if err != nil { + w.Error(err) + return + } + + w.JSON(repo) +} + +func (repositoryCtl RepositoryController) UpdateRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.UpdateRepositoryJSONRequestBody, userName string, repositoryName string) { + user, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) + if err != nil { + w.Error(err) + return + } + + repo, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ + CreatorID: user.ID, + Name: utils.String(repositoryName), + }) + if err != nil { + w.Error(err) + return + } + + err = repositoryCtl.Repo.RepositoryRepo().UpdateByID(ctx, models.NewUpdateRepoParams(repo.ID).SetDescription(utils.StringValue(body.Description))) + if err != nil { + w.Error(err) + return + } +} diff --git a/controller/user_ctl.go b/controller/user_ctl.go index baa16b38..d28e4e81 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -2,7 +2,6 @@ package controller import ( "context" - "encoding/json" "net/http" "github.com/jiaozifs/jiaozifs/config" @@ -24,13 +23,10 @@ type UserController struct { Config *config.Config } -func (userCtl UserController) Login(ctx context.Context, w *api.JiaozifsResponse, r *http.Request) { - // Decode requestBody - var login auth.Login - decoder := json.NewDecoder(r.Body) - if err := decoder.Decode(&login); err != nil { - w.Error(err) - return +func (userCtl UserController) Login(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.LoginJSONRequestBody) { + login := auth.Login{ + Username: body.Username, + Password: body.Password, } // perform login @@ -42,14 +38,13 @@ func (userCtl UserController) Login(ctx context.Context, w *api.JiaozifsResponse w.JSON(authToken) } -func (userCtl UserController) Register(ctx context.Context, w *api.JiaozifsResponse, r *http.Request) { - // Decode requestBody - var register auth.Register - decoder := json.NewDecoder(r.Body) - if err := decoder.Decode(®ister); err != nil { - w.Error(err) - return +func (userCtl UserController) Register(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.RegisterJSONRequestBody) { + register := auth.Register{ + Username: body.Username, + Email: string(body.Email), + Password: body.Password, } + // perform register err := register.Register(ctx, userCtl.Repo.UserRepo()) if err != nil { diff --git a/controller/wip_ctl.go b/controller/wip_ctl.go index d4f18484..58026bd8 100644 --- a/controller/wip_ctl.go +++ b/controller/wip_ctl.go @@ -4,14 +4,12 @@ import ( "bytes" "context" "encoding/hex" - "errors" "fmt" "net/http" "time" "github.com/jiaozifs/jiaozifs/versionmgr" - "github.com/google/uuid" openapi_types "github.com/oapi-codegen/runtime/types" "github.com/jiaozifs/jiaozifs/utils" @@ -28,15 +26,15 @@ type WipController struct { } func (wipCtl WipController) CreateWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repository string, refID openapi_types.UUID, params api.CreateWipParams) { - user, err := wipCtl.Repo.UserRepo().Get(ctx, &models.GetUserParam{Name: utils.String(userName)}) + user, err := wipCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) if err != nil { w.Error(err) return } repo, err := wipCtl.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ - CreateID: user.ID, - Name: utils.String(repository), + CreatorID: user.ID, + Name: utils.String(repository), }) if err != nil { w.Error(err) @@ -56,7 +54,7 @@ func (wipCtl WipController) CreateWip(ctx context.Context, w *api.JiaozifsRespon RefID: refID, State: 0, Name: params.Name, - CreateID: user.ID, + CreatorID: user.ID, CreatedAt: time.Now(), UpdatedAt: time.Now(), } @@ -69,28 +67,24 @@ func (wipCtl WipController) CreateWip(ctx context.Context, w *api.JiaozifsRespon } func (wipCtl WipController) GetWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repositoryName string, refID openapi_types.UUID) { - user, err := wipCtl.Repo.UserRepo().Get(ctx, &models.GetUserParam{Name: utils.String(userName)}) + user, err := wipCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) if err != nil { w.Error(err) return } - repository, err := wipCtl.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{Name: utils.String(repositoryName)}) + repository, err := wipCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName)) if err != nil { w.Error(err) return } - wip, err := wipCtl.Repo.WipRepo().Get(ctx, &models.GetWipParam{ + wip, err := wipCtl.Repo.WipRepo().Get(ctx, &models.GetWipParams{ RefID: refID, - CreateID: user.ID, + CreatorID: user.ID, RepositoryID: repository.ID, }) if err != nil { - if errors.Is(err, models.ErrNotFound) { - w.NotFound() - return - } w.Error(err) return } @@ -99,7 +93,7 @@ func (wipCtl WipController) GetWip(ctx context.Context, w *api.JiaozifsResponse, } func (wipCtl WipController) ListWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repositoryName string) { - user, err := wipCtl.Repo.UserRepo().Get(ctx, &models.GetUserParam{Name: utils.String(userName)}) + user, err := wipCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) if err != nil { w.Error(err) return @@ -111,10 +105,7 @@ func (wipCtl WipController) ListWip(ctx context.Context, w *api.JiaozifsResponse return } - wips, err := wipCtl.Repo.WipRepo().List(ctx, &models.ListWipParam{ - CreateID: user.ID, - RepositoryID: repository.ID, - }) + wips, err := wipCtl.Repo.WipRepo().List(ctx, models.NewListWipParams().SetCreatorID(user.ID).SetRepositoryID(repository.ID)) if err != nil { w.Error(err) return @@ -124,19 +115,19 @@ func (wipCtl WipController) ListWip(ctx context.Context, w *api.JiaozifsResponse } func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repositoryName string, refID openapi_types.UUID, params api.CommitWipParams) { - user, err := wipCtl.Repo.UserRepo().Get(ctx, &models.GetUserParam{Name: utils.String(userName)}) + user, err := wipCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) if err != nil { w.Error(err) return } - repository, err := wipCtl.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{Name: utils.String(repositoryName)}) + repository, err := wipCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName)) if err != nil { w.Error(err) return } - ref, err := wipCtl.Repo.RefRepo().Get(ctx, &models.GetRefParams{ID: refID}) + ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetID(refID)) if err != nil { w.Error(err) return @@ -148,9 +139,9 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon return } - wip, err := wipCtl.Repo.WipRepo().Get(ctx, &models.GetWipParam{ + wip, err := wipCtl.Repo.WipRepo().Get(ctx, &models.GetWipParams{ RefID: refID, - CreateID: user.ID, + CreatorID: user.ID, RepositoryID: repository.ID, }) if err != nil { @@ -176,12 +167,12 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon } wip.BaseTree = commit.Commit().TreeHash //set for response - err = repo.WipRepo().UpdateBaseHash(ctx, wip.ID, commit.Commit().TreeHash) + err = repo.WipRepo().UpdateByID(ctx, models.NewUpdateWipParams(wip.ID).SetBaseTree(commit.Commit().TreeHash)) if err != nil { return err } - return repo.RefRepo().UpdateCommitHash(ctx, refID, commit.Commit().Hash) + return repo.RefRepo().UpdateByID(ctx, models.NewUpdateRefParams(refID).SetCommitHash(commit.Commit().Hash)) }) if err != nil { w.Error(err) @@ -193,24 +184,24 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon // DeleteWip delete a active working in process func (wipCtl WipController) DeleteWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repositoryName string, refID openapi_types.UUID) { - user, err := wipCtl.Repo.UserRepo().Get(ctx, &models.GetUserParam{Name: utils.String(userName)}) + user, err := wipCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) if err != nil { w.Error(err) return } - repository, err := wipCtl.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{Name: utils.String(repositoryName)}) + repository, err := wipCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName)) if err != nil { w.Error(err) return } - err = wipCtl.Repo.WipRepo().Delete(ctx, &models.DeleteWipParam{ - ID: uuid.UUID{}, - CreateID: user.ID, - RepositoryID: repository.ID, - RefID: refID, - }) + deleteWipParams := models.NewDeleteWipParams(). + SetCreatorID(user.ID). + SetRepositoryID(repository.ID). + SetRefID(refID) + + err = wipCtl.Repo.WipRepo().Delete(ctx, deleteWipParams) if err != nil { w.Error(err) return diff --git a/models/object.go b/models/object.go index 0ad94c56..38f337d6 100644 --- a/models/object.go +++ b/models/object.go @@ -358,6 +358,15 @@ type GetObjParams struct { Hash hash.Hash } +func NewGetObjParams() *GetObjParams { + return &GetObjParams{} +} + +func (gop *GetObjParams) SetHash(hash hash.Hash) *GetObjParams { + gop.Hash = hash + return gop +} + type IObjectRepo interface { Insert(ctx context.Context, repo *Object) (*Object, error) Get(ctx context.Context, params *GetObjParams) (*Object, error) diff --git a/models/object_test.go b/models/object_test.go index b7582834..f8f016fa 100644 --- a/models/object_test.go +++ b/models/object_test.go @@ -32,9 +32,7 @@ func TestObjectRepo_Insert(t *testing.T) { require.NoError(t, err) require.Equal(t, 1, len(list)) - ref, err := repo.Get(ctx, &models.GetObjParams{ - Hash: newObj.Hash, - }) + ref, err := repo.Get(ctx, models.NewGetObjParams().SetHash(newObj.Hash)) require.NoError(t, err) require.True(t, cmp.Equal(newObj, ref, dbTimeCmpOpt)) diff --git a/models/ref.go b/models/ref.go index 0f1d7328..141aeb7b 100644 --- a/models/ref.go +++ b/models/ref.go @@ -19,9 +19,9 @@ type Ref struct { // Path name/path of branch Name string `bun:"name,notnull"` // Description - Description string `bun:"description"` - // CreateId who create this branch - CreateID uuid.UUID `bun:"create_id,type:uuid,notnull"` + Description *string `bun:"description"` + // CreatorID who create this branch + CreatorID uuid.UUID `bun:"creator_id,type:uuid,notnull"` CreatedAt time.Time `bun:"created_at"` UpdatedAt time.Time `bun:"updated_at"` @@ -33,9 +33,43 @@ type GetRefParams struct { Name *string } +func NewGetRefParams() *GetRefParams { + return &GetRefParams{} +} + +func (gup *GetRefParams) SetID(id uuid.UUID) *GetRefParams { + gup.ID = id + return gup +} + +func (gup *GetRefParams) SetRepositoryID(repositoryID uuid.UUID) *GetRefParams { + gup.RepositoryID = repositoryID + return gup +} + +func (gup *GetRefParams) SetName(name string) *GetRefParams { + gup.Name = &name + return gup +} + +type UpdateRefParams struct { + bun.BaseModel `bun:"table:ref"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` + CommitHash hash.Hash `bun:"commit_hash,type:bytea,notnull"` +} + +func NewUpdateRefParams(id uuid.UUID) *UpdateRefParams { + return &UpdateRefParams{ID: id} +} + +func (up *UpdateRefParams) SetCommitHash(commitHash hash.Hash) *UpdateRefParams { + up.CommitHash = commitHash + return up +} + type IRefRepo interface { Insert(ctx context.Context, repo *Ref) (*Ref, error) - UpdateCommitHash(ctx context.Context, id uuid.UUID, commitHash hash.Hash) error + UpdateByID(ctx context.Context, params *UpdateRefParams) error Get(ctx context.Context, id *GetRefParams) (*Ref, error) } @@ -76,7 +110,7 @@ func (r RefRepo) Get(ctx context.Context, params *GetRefParams) (*Ref, error) { return repo, query.Limit(1).Scan(ctx, repo) } -func (r RefRepo) UpdateCommitHash(ctx context.Context, id uuid.UUID, commitHash hash.Hash) error { - _, err := r.db.NewUpdate().Model((*Ref)(nil)).SetColumn("commit_hash", "?", commitHash).Where("id = ?", id).Exec(ctx) +func (r RefRepo) UpdateByID(ctx context.Context, updateModel *UpdateRefParams) error { + _, err := r.db.NewUpdate().Model(updateModel).WherePK().Exec(ctx) return err } diff --git a/models/ref_test.go b/models/ref_test.go index 19bc93a3..afd5fdbc 100644 --- a/models/ref_test.go +++ b/models/ref_test.go @@ -4,7 +4,6 @@ import ( "context" "testing" - "github.com/jiaozifs/jiaozifs/utils" "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/brianvoe/gofakeit/v6" @@ -28,17 +27,17 @@ func TestRefRepoInsert(t *testing.T) { require.NoError(t, err) require.NotEqual(t, uuid.Nil, newRef.ID) - ref, err := repo.Get(ctx, &models.GetRefParams{ - ID: newRef.ID, - RepositoryID: newRef.RepositoryID, - Name: utils.String(newRef.Name), - }) + getRefParams := models.NewGetRefParams(). + SetID(newRef.ID). + SetRepositoryID(newRef.RepositoryID). + SetName(newRef.Name) + ref, err := repo.Get(ctx, getRefParams) require.NoError(t, err) require.True(t, cmp.Equal(refModel, ref, dbTimeCmpOpt)) mockHash := hash.Hash("mock hash") - err = repo.UpdateCommitHash(ctx, ref.ID, mockHash) + err = repo.UpdateByID(ctx, models.NewUpdateRefParams(newRef.ID).SetCommitHash(mockHash)) require.NoError(t, err) refAfterUpdated, err := repo.Get(ctx, &models.GetRefParams{ diff --git a/models/repo_test.go b/models/repo_test.go index 66ebb409..2f334dfe 100644 --- a/models/repo_test.go +++ b/models/repo_test.go @@ -57,7 +57,7 @@ func TestRepoTransaction(t *testing.T) { }) require.Error(t, err) - _, err = pgRepo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ID: id}) + _, err = pgRepo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetID(id)) require.True(t, errors.Is(err, sql.ErrNoRows)) }) } diff --git a/models/repository.go b/models/repository.go index f6369f43..470c3374 100644 --- a/models/repository.go +++ b/models/repository.go @@ -12,23 +12,94 @@ type Repository struct { bun.BaseModel `bun:"table:repository"` ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` Name string `bun:"name,notnull"` - Description string `bun:"description"` + Description *string `bun:"description"` HEAD string `bun:"head,notnull"` - CreateID uuid.UUID `bun:"create_id,type:uuid,notnull"` + CreatorID uuid.UUID `bun:"creator_id,type:uuid,notnull"` CreatedAt time.Time `bun:"created_at"` UpdatedAt time.Time `bun:"updated_at"` } type GetRepoParams struct { - ID uuid.UUID - CreateID uuid.UUID - Name *string + ID uuid.UUID + CreatorID uuid.UUID + Name *string +} + +func NewGetRepoParams() *GetRepoParams { + return &GetRepoParams{} +} + +func (gup *GetRepoParams) SetID(id uuid.UUID) *GetRepoParams { + gup.ID = id + return gup +} + +func (gup *GetRepoParams) SetCreatorID(creatorID uuid.UUID) *GetRepoParams { + gup.CreatorID = creatorID + return gup +} + +func (gup *GetRepoParams) SetName(name string) *GetRepoParams { + gup.Name = &name + return gup +} + +type ListRepoParams struct { + ID uuid.UUID + CreatorID uuid.UUID +} + +func NewListRepoParam() *ListRepoParams { + return &ListRepoParams{} +} + +func (lrp *ListRepoParams) SetID(id uuid.UUID) *ListRepoParams { + lrp.ID = id + return lrp +} + +func (lrp *ListRepoParams) SetCreatorID(creatorID uuid.UUID) *ListRepoParams { + lrp.CreatorID = creatorID + return lrp +} + +type DeleteRepoParams struct { + ID uuid.UUID +} + +func NewDeleteRepoParams() *DeleteRepoParams { + return &DeleteRepoParams{} +} + +func (drp *DeleteRepoParams) SetID(id uuid.UUID) *DeleteRepoParams { + drp.ID = id + return drp +} + +type UpdateRepoParams struct { + bun.BaseModel `bun:"table:repository"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` + Description *string `bun:"description"` +} + +func NewUpdateRepoParams(id uuid.UUID) *UpdateRepoParams { + return &UpdateRepoParams{ + ID: id, + } +} + +func (up *UpdateRepoParams) SetDescription(description string) *UpdateRepoParams { + up.Description = &description + return up } type IRepositoryRepo interface { Insert(ctx context.Context, repo *Repository) (*Repository, error) Get(ctx context.Context, params *GetRepoParams) (*Repository, error) + List(ctx context.Context, params *ListRepoParams) ([]*Repository, error) + Delete(ctx context.Context, params *DeleteRepoParams) error + UpdateByID(ctx context.Context, updateModel *UpdateRepoParams) error } var _ IRepositoryRepo = (*RepositoryRepo)(nil) @@ -57,8 +128,8 @@ func (r *RepositoryRepo) Get(ctx context.Context, params *GetRepoParams) (*Repos query = query.Where("id = ?", params.ID) } - if uuid.Nil != params.CreateID { - query = query.Where("create_id = ?", params.CreateID) + if uuid.Nil != params.CreatorID { + query = query.Where("creator_id = ?", params.CreatorID) } if params.Name != nil { @@ -67,3 +138,29 @@ func (r *RepositoryRepo) Get(ctx context.Context, params *GetRepoParams) (*Repos return repo, query.Limit(1).Scan(ctx, repo) } + +func (r *RepositoryRepo) List(ctx context.Context, params *ListRepoParams) ([]*Repository, error) { + repos := []*Repository{} + query := r.db.NewSelect().Model((*Repository)(nil)) + + if uuid.Nil != params.CreatorID { + query = query.Where("creator_id = ?", params.CreatorID) + } + + return repos, query.Scan(ctx, &repos) +} + +func (r *RepositoryRepo) Delete(ctx context.Context, params *DeleteRepoParams) error { + query := r.db.NewDelete().Model((*Repository)(nil)) + if uuid.Nil != params.ID { + query = query.Where("id = ?", params.ID) + } + + _, err := query.Exec(ctx) + return err +} + +func (r *RepositoryRepo) UpdateByID(ctx context.Context, updateModel *UpdateRepoParams) error { + _, err := r.db.NewUpdate().Model(updateModel).WherePK().Exec(ctx) + return err +} diff --git a/models/repository_test.go b/models/repository_test.go index 6319a541..3d60dc9f 100644 --- a/models/repository_test.go +++ b/models/repository_test.go @@ -21,14 +21,36 @@ func TestRepositoryRepo_Insert(t *testing.T) { repoModel := &models.Repository{} require.NoError(t, gofakeit.Struct(repoModel)) - newUser, err := repo.Insert(ctx, repoModel) + newRepo, err := repo.Insert(ctx, repoModel) require.NoError(t, err) - require.NotEqual(t, uuid.Nil, newUser.ID) + require.NotEqual(t, uuid.Nil, newRepo.ID) - user, err := repo.Get(ctx, &models.GetRepoParams{ - ID: newUser.ID, - }) + user, err := repo.Get(ctx, models.NewGetRepoParams().SetID(newRepo.ID)) require.NoError(t, err) - require.True(t, cmp.Equal(repoModel, user, dbTimeCmpOpt)) + + err = repo.UpdateByID(ctx, models.NewUpdateRepoParams(newRepo.ID).SetDescription("description")) + require.NoError(t, err) + user, err = repo.Get(ctx, models.NewGetRepoParams().SetID(newRepo.ID)) + require.NoError(t, err) + + require.Equal(t, "description", *user.Description) + //insert secondary + secModel := &models.Repository{} + require.NoError(t, gofakeit.Struct(secModel)) + secModel.CreatorID = repoModel.CreatorID + secRepo, err := repo.Insert(ctx, secModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, secRepo.ID) + + //list + repos, err := repo.List(ctx, models.NewListRepoParam().SetCreatorID(secModel.CreatorID)) + require.NoError(t, err) + require.Len(t, repos, 2) + + //delete + err = repo.Delete(ctx, models.NewDeleteRepoParams().SetID(secRepo.ID)) + require.NoError(t, err) + _, err = repo.Get(ctx, models.NewGetRepoParams().SetID(secRepo.ID)) + require.ErrorIs(t, err, models.ErrNotFound) } diff --git a/models/user.go b/models/user.go index df760a3f..daf7d4be 100644 --- a/models/user.go +++ b/models/user.go @@ -22,16 +22,39 @@ type User struct { UpdatedAt time.Time `bun:"updated_at"` } -type GetUserParam struct { +type GetUserParams struct { ID uuid.UUID Name *string Email *string } -type CountUserParams = GetUserParam +func NewGetUserParams() *GetUserParams { + return &GetUserParams{} +} + +func (gup *GetUserParams) SetID(id uuid.UUID) *GetUserParams { + gup.ID = id + return gup +} + +func (gup *GetUserParams) SetName(name string) *GetUserParams { + gup.Name = &name + return gup +} + +func (gup *GetUserParams) SetEmail(email string) *GetUserParams { + gup.Email = &email + return gup +} + +type CountUserParams = GetUserParams + +func NewCountUserParam() *CountUserParams { + return &CountUserParams{} +} type IUserRepo interface { - Get(ctx context.Context, params *GetUserParam) (*User, error) + Get(ctx context.Context, params *GetUserParams) (*User, error) Count(ctx context.Context, params *CountUserParams) (int, error) Insert(ctx context.Context, user *User) (*User, error) GetEPByName(ctx context.Context, name string) (string, error) @@ -47,7 +70,7 @@ func NewUserRepo(db bun.IDB) IUserRepo { return &UserRepo{db: db} } -func (userRepo *UserRepo) Get(ctx context.Context, params *GetUserParam) (*User, error) { +func (userRepo *UserRepo) Get(ctx context.Context, params *GetUserParams) (*User, error) { user := &User{} query := userRepo.db.NewSelect().Model(user) @@ -66,7 +89,7 @@ func (userRepo *UserRepo) Get(ctx context.Context, params *GetUserParam) (*User, return user, query.Limit(1).Scan(ctx) } -func (userRepo *UserRepo) Count(ctx context.Context, params *GetUserParam) (int, error) { +func (userRepo *UserRepo) Count(ctx context.Context, params *GetUserParams) (int, error) { query := userRepo.db.NewSelect().Model((*User)(nil)) if uuid.Nil != params.ID { diff --git a/models/user_test.go b/models/user_test.go index 1817c3b5..a3aaeed7 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -5,8 +5,6 @@ import ( "testing" "time" - "github.com/jiaozifs/jiaozifs/utils" - "github.com/jiaozifs/jiaozifs/testhelper" "github.com/brianvoe/gofakeit/v6" @@ -37,7 +35,7 @@ func TestNewUserRepo(t *testing.T) { require.NoError(t, err) require.NotEqual(t, uuid.Nil, newUser.ID) - user, err := repo.Get(ctx, &models.GetUserParam{ID: newUser.ID}) + user, err := repo.Get(ctx, models.NewGetUserParams().SetID(newUser.ID)) require.NoError(t, err) require.True(t, cmp.Equal(userModel, user, dbTimeCmpOpt)) @@ -46,11 +44,11 @@ func TestNewUserRepo(t *testing.T) { require.NoError(t, err) require.True(t, cmp.Equal(userModel.EncryptedPassword, ep)) - userByEmail, err := repo.Get(ctx, &models.GetUserParam{Email: utils.String(newUser.Email)}) + userByEmail, err := repo.Get(ctx, models.NewGetUserParams().SetEmail(newUser.Email)) require.NoError(t, err) require.True(t, cmp.Equal(userModel, userByEmail, dbTimeCmpOpt)) - userByName, err := repo.Get(ctx, &models.GetUserParam{Name: utils.String(newUser.Name)}) + userByName, err := repo.Get(ctx, models.NewGetUserParams().SetName(newUser.Name)) require.NoError(t, err) require.True(t, cmp.Equal(userModel, userByName, dbTimeCmpOpt)) } diff --git a/models/wip.go b/models/wip.go index a57df21c..4854ef70 100644 --- a/models/wip.go +++ b/models/wip.go @@ -24,39 +24,132 @@ type WorkingInProcess struct { BaseTree hash.Hash `bun:"base_tree,type:bytea,notnull"` RepositoryID uuid.UUID `bun:"repository_id,type:uuid,notnull"` RefID uuid.UUID `bun:"ref_id,type:uuid,notnull"` - State WipState `bun:"state"` - CreateID uuid.UUID `bun:"create_id,type:uuid,notnull"` + State WipState `bun:"state,notnull"` + CreatorID uuid.UUID `bun:"creator_id,type:uuid,notnull"` CreatedAt time.Time `bun:"created_at"` UpdatedAt time.Time `bun:"updated_at"` } -type GetWipParam struct { +type GetWipParams struct { ID uuid.UUID - CreateID uuid.UUID + CreatorID uuid.UUID RepositoryID uuid.UUID RefID uuid.UUID } -type ListWipParam struct { - CreateID uuid.UUID +func NewGetWipParams() *GetWipParams { + return &GetWipParams{} +} + +func (gwp *GetWipParams) SetID(id uuid.UUID) *GetWipParams { + gwp.ID = id + return gwp +} + +func (gwp *GetWipParams) SetCreatorID(creatorID uuid.UUID) *GetWipParams { + gwp.CreatorID = creatorID + return gwp +} + +func (gwp *GetWipParams) SetRepositoryID(repositoryID uuid.UUID) *GetWipParams { + gwp.RepositoryID = repositoryID + return gwp +} + +func (gwp *GetWipParams) SetRefID(refID uuid.UUID) *GetWipParams { + gwp.RefID = refID + return gwp +} + +type ListWipParams struct { + CreatorID uuid.UUID RepositoryID uuid.UUID RefID uuid.UUID } -type DeleteWipParam struct { +func NewListWipParams() *ListWipParams { + return &ListWipParams{} +} +func (lwp *ListWipParams) SetCreatorID(creatorID uuid.UUID) *ListWipParams { + lwp.CreatorID = creatorID + return lwp +} + +func (lwp *ListWipParams) SetRepositoryID(repositoryID uuid.UUID) *ListWipParams { + lwp.RepositoryID = repositoryID + return lwp +} + +func (lwp *ListWipParams) SetRefID(refID uuid.UUID) *ListWipParams { + lwp.RefID = refID + return lwp +} + +type DeleteWipParams struct { ID uuid.UUID - CreateID uuid.UUID + CreatorID uuid.UUID RepositoryID uuid.UUID RefID uuid.UUID } + +func NewDeleteWipParams() *DeleteWipParams { + return &DeleteWipParams{} +} + +func (dwp *DeleteWipParams) SetID(id uuid.UUID) *DeleteWipParams { + dwp.ID = id + return dwp +} + +func (dwp *DeleteWipParams) SetCreatorID(creatorID uuid.UUID) *DeleteWipParams { + dwp.CreatorID = creatorID + return dwp +} + +func (dwp *DeleteWipParams) SetRepositoryID(repositoryID uuid.UUID) *DeleteWipParams { + dwp.RepositoryID = repositoryID + return dwp +} + +func (dwp *DeleteWipParams) SetRefID(refID uuid.UUID) *DeleteWipParams { + dwp.RefID = refID + return dwp +} + +type UpdateWipParams struct { + bun.BaseModel `bun:"table:wip"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` + CurrentTree hash.Hash `bun:"current_tree,type:bytea,notnull"` + BaseTree hash.Hash `bun:"base_tree,type:bytea,notnull"` + State WipState `bun:"state,notnull"` + UpdatedAt time.Time `bun:"updated_at"` +} + +func NewUpdateWipParams(ID uuid.UUID) *UpdateWipParams { + return &UpdateWipParams{ID: ID, UpdatedAt: time.Now()} +} + +func (up *UpdateWipParams) SetCurrentTree(currentTree hash.Hash) *UpdateWipParams { + up.CurrentTree = currentTree + return up +} + +func (up *UpdateWipParams) SetBaseTree(baseTree hash.Hash) *UpdateWipParams { + up.BaseTree = baseTree + return up +} + +func (up *UpdateWipParams) SetState(state WipState) *UpdateWipParams { + up.State = state + return up +} + type IWipRepo interface { Insert(ctx context.Context, repo *WorkingInProcess) (*WorkingInProcess, error) - Get(ctx context.Context, params *GetWipParam) (*WorkingInProcess, error) - List(ctx context.Context, params *ListWipParam) ([]*WorkingInProcess, error) - Delete(ctx context.Context, params *DeleteWipParam) error - UpdateCurrentHash(ctx context.Context, id uuid.UUID, newTreeHash hash.Hash) error - UpdateBaseHash(ctx context.Context, id uuid.UUID, treeHash hash.Hash) error - UpdateState(ctx context.Context, id uuid.UUID, state WipState) error + Get(ctx context.Context, params *GetWipParams) (*WorkingInProcess, error) + List(ctx context.Context, params *ListWipParams) ([]*WorkingInProcess, error) + Delete(ctx context.Context, params *DeleteWipParams) error + UpdateByID(ctx context.Context, params *UpdateWipParams) error } var _ IWipRepo = (*WipRepo)(nil) @@ -78,7 +171,7 @@ func (s *WipRepo) Insert(ctx context.Context, repo *WorkingInProcess) (*WorkingI } // Get wip by a group of conditions -func (s *WipRepo) Get(ctx context.Context, params *GetWipParam) (*WorkingInProcess, error) { +func (s *WipRepo) Get(ctx context.Context, params *GetWipParams) (*WorkingInProcess, error) { repo := &WorkingInProcess{} query := s.db.NewSelect().Model(repo) @@ -86,8 +179,8 @@ func (s *WipRepo) Get(ctx context.Context, params *GetWipParam) (*WorkingInProce query = query.Where("id = ?", params.ID) } - if uuid.Nil != params.CreateID { - query = query.Where("create_id = ?", params.CreateID) + if uuid.Nil != params.CreatorID { + query = query.Where("creator_id = ?", params.CreatorID) } if uuid.Nil != params.RepositoryID { @@ -101,12 +194,12 @@ func (s *WipRepo) Get(ctx context.Context, params *GetWipParam) (*WorkingInProce return repo, query.Limit(1).Scan(ctx, repo) } -func (s *WipRepo) List(ctx context.Context, params *ListWipParam) ([]*WorkingInProcess, error) { +func (s *WipRepo) List(ctx context.Context, params *ListWipParams) ([]*WorkingInProcess, error) { var resp []*WorkingInProcess query := s.db.NewSelect().Model((*WorkingInProcess)(nil)) - if uuid.Nil != params.CreateID { - query = query.Where("create_id = ?", params.CreateID) + if uuid.Nil != params.CreatorID { + query = query.Where("creator_id = ?", params.CreatorID) } if uuid.Nil != params.RepositoryID { @@ -120,46 +213,12 @@ func (s *WipRepo) List(ctx context.Context, params *ListWipParam) ([]*WorkingInP return resp, query.Scan(ctx, &resp) } -// UpdateCurrentHash update current hash -func (s *WipRepo) UpdateCurrentHash(ctx context.Context, id uuid.UUID, newTreeHash hash.Hash) error { - wip := &WorkingInProcess{ - CurrentTree: newTreeHash, - } - _, err := s.db.NewUpdate().Model(wip).OmitZero().Column("current_tree"). - Where("id = ?", id). - Exec(ctx) - return err -} - -// UpdateBaseHash update base hash -func (s *WipRepo) UpdateBaseHash(ctx context.Context, id uuid.UUID, treeHash hash.Hash) error { - wip := &WorkingInProcess{ - BaseTree: treeHash, - } - _, err := s.db.NewUpdate().Model(wip).OmitZero().Column("base_tree"). - Where("id = ?", id). - Exec(ctx) - return err -} - -// UpdateState update wip state -func (s *WipRepo) UpdateState(ctx context.Context, id uuid.UUID, state WipState) error { - wip := &WorkingInProcess{ - State: state, - } - - _, err := s.db.NewUpdate().Model(wip).OmitZero().Column("state"). - Where("id = ?", id). - Exec(ctx) - return err -} - // Delete remove wip in table by id -func (s *WipRepo) Delete(ctx context.Context, params *DeleteWipParam) error { +func (s *WipRepo) Delete(ctx context.Context, params *DeleteWipParams) error { query := s.db.NewDelete().Model((*WorkingInProcess)(nil)) - if uuid.Nil != params.CreateID { - query = query.Where("create_id = ?", params.CreateID) + if uuid.Nil != params.CreatorID { + query = query.Where("creator_id = ?", params.CreatorID) } if uuid.Nil != params.RepositoryID { @@ -176,3 +235,8 @@ func (s *WipRepo) Delete(ctx context.Context, params *DeleteWipParam) error { _, err := query.Exec(ctx) return err } + +func (s *WipRepo) UpdateByID(ctx context.Context, updateModel *UpdateWipParams) error { + _, err := s.db.NewUpdate().Model(updateModel).WherePK().OmitZero().Exec(ctx) + return err +} diff --git a/models/wip_test.go b/models/wip_test.go index c82c6777..ac4ec3ce 100644 --- a/models/wip_test.go +++ b/models/wip_test.go @@ -27,60 +27,79 @@ func TestWipRepo(t *testing.T) { require.NoError(t, err) require.NotEqual(t, uuid.Nil, newWipModel.ID) - user, err := repo.Get(ctx, &models.GetWipParam{ - ID: newWipModel.ID, - CreateID: newWipModel.CreateID, - RepositoryID: newWipModel.RepositoryID, - RefID: newWipModel.RefID, - }) + getWipParams := models.NewGetWipParams(). + SetID(newWipModel.ID). + SetCreatorID(newWipModel.CreatorID). + SetRepositoryID(newWipModel.RepositoryID). + SetRefID(newWipModel.RefID) + user, err := repo.Get(ctx, getWipParams) require.NoError(t, err) require.True(t, cmp.Equal(newWipModel, user, dbTimeCmpOpt)) - err = repo.UpdateCurrentHash(ctx, newWipModel.ID, hash.Hash("mock hash")) - require.NoError(t, err) - updatedUser, err := repo.Get(ctx, &models.GetWipParam{ID: newWipModel.ID}) - require.NoError(t, err) - require.Equal(t, "mock hash", string(updatedUser.CurrentTree)) - - err = repo.UpdateBaseHash(ctx, newWipModel.ID, hash.Hash("mock base hash")) - require.NoError(t, err) - updatedUser, err = repo.Get(ctx, &models.GetWipParam{ID: newWipModel.ID}) - require.NoError(t, err) - require.Equal(t, "mock base hash", string(updatedUser.BaseTree)) - - err = repo.UpdateState(ctx, newWipModel.ID, models.Completed) - require.NoError(t, err) - updatedUser, err = repo.Get(ctx, &models.GetWipParam{ID: newWipModel.ID}) - require.NoError(t, err) - require.Equal(t, models.Completed, updatedUser.State) - t.Run("list", func(t *testing.T) { secWipModel := &models.WorkingInProcess{} require.NoError(t, gofakeit.Struct(secWipModel)) - secWipModel.CreateID = newWipModel.CreateID + secWipModel.CreatorID = newWipModel.CreatorID secWipModel.RepositoryID = newWipModel.RepositoryID secWipModel.RefID = newWipModel.RefID secNewWipModel, err := repo.Insert(ctx, secWipModel) require.NoError(t, err) require.NotEqual(t, uuid.Nil, secNewWipModel.ID) - list, err := repo.List(ctx, &models.ListWipParam{ - CreateID: secNewWipModel.CreateID, - RepositoryID: secNewWipModel.RepositoryID, - RefID: secWipModel.RefID, - }) + listParams := models.NewListWipParams(). + SetCreatorID(secNewWipModel.CreatorID). + SetRepositoryID(secNewWipModel.RepositoryID). + SetRefID(secNewWipModel.RefID) + + list, err := repo.List(ctx, listParams) require.NoError(t, err) require.Len(t, list, 2) - err = repo.Delete(ctx, &models.DeleteWipParam{ - ID: secWipModel.ID, - CreateID: secWipModel.CreateID, - RepositoryID: secWipModel.RepositoryID, - RefID: secWipModel.RefID, - }) + deleteParams := models.NewDeleteWipParams(). + SetID(secWipModel.ID). + SetCreatorID(secWipModel.CreatorID). + SetRepositoryID(secWipModel.RepositoryID). + SetRefID(secWipModel.RefID) + err = repo.Delete(ctx, deleteParams) require.NoError(t, err) - _, err = repo.Get(ctx, &models.GetWipParam{ID: secWipModel.ID}) + _, err = repo.Get(ctx, models.NewGetWipParams().SetID(secWipModel.ID)) require.ErrorIs(t, err, models.ErrNotFound) }) } + +func TestWipRepoUpdateByID(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + repo := models.NewWipRepo(db) + + wipModel := &models.WorkingInProcess{} + require.NoError(t, gofakeit.Struct(wipModel)) + newWipModel, err := repo.Insert(ctx, wipModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newWipModel.ID) + + getWipParams := models.NewGetWipParams(). + SetID(newWipModel.ID). + SetCreatorID(newWipModel.CreatorID). + SetRepositoryID(newWipModel.RepositoryID). + SetRefID(newWipModel.RefID) + user, err := repo.Get(ctx, getWipParams) + require.NoError(t, err) + require.True(t, cmp.Equal(newWipModel, user, dbTimeCmpOpt)) + + updateModel := models.NewUpdateWipParams(newWipModel.ID). + SetState(models.Completed). + SetBaseTree(hash.Hash("mock base hash")). + SetCurrentTree(hash.Hash("mock hash")) + + err = repo.UpdateByID(ctx, updateModel) + require.NoError(t, err) + updatedUser, err := repo.Get(ctx, models.NewGetWipParams().SetID(newWipModel.ID)) + require.NoError(t, err) + require.Equal(t, models.Completed, updatedUser.State) + require.Equal(t, "mock base hash", string(updatedUser.BaseTree)) + require.Equal(t, "mock hash", string(updatedUser.CurrentTree)) +} diff --git a/versionmgr/commit.go b/versionmgr/commit.go index ae1003cb..00336412 100644 --- a/versionmgr/commit.go +++ b/versionmgr/commit.go @@ -46,16 +46,12 @@ func (commitOp *CommitOp) Commit() *models.Commit { // AddCommit append a new commit to current head, read changes from wip, than create a new commit with parent point to current head, // and replace tree hash with wip's currentTreeHash. func (commitOp *CommitOp) AddCommit(ctx context.Context, committer *models.User, wipID uuid.UUID, msg string) (*CommitOp, error) { - wip, err := commitOp.wip.Get(ctx, &models.GetWipParam{ - ID: wipID, - }) + wip, err := commitOp.wip.Get(ctx, models.NewGetWipParams().SetID(wipID)) if err != nil { return nil, err } - creator, err := commitOp.user.Get(ctx, &models.GetUserParam{ - ID: wip.CreateID, - }) + creator, err := commitOp.user.Get(ctx, models.NewGetUserParams().SetID(wip.CreatorID)) if err != nil { return nil, err } diff --git a/versionmgr/commit_test.go b/versionmgr/commit_test.go index ec308343..86bd2bb4 100644 --- a/versionmgr/commit_test.go +++ b/versionmgr/commit_test.go @@ -6,6 +6,8 @@ import ( "testing" "time" + "github.com/jiaozifs/jiaozifs/utils" + "github.com/go-git/go-git/v5/utils/merkletrie" "github.com/google/uuid" @@ -317,9 +319,9 @@ func makeUser(ctx context.Context, userRepo models.IUserRepo, name string) (*mod func makeRepository(ctx context.Context, repoRepo models.IRepositoryRepo, name string) (*models.Repository, error) { user := &models.Repository{ Name: name, - Description: "", + Description: utils.String("test"), HEAD: "main", - CreateID: uuid.UUID{}, + CreatorID: uuid.UUID{}, CreatedAt: time.Time{}, UpdatedAt: time.Time{}, } @@ -357,8 +359,8 @@ func makeRef(ctx context.Context, refRepo models.IRefRepo, name string, repoID u RepositoryID: repoID, CommitHash: commitHash, Name: name, - Description: "", - CreateID: uuid.UUID{}, + Description: utils.String("test"), + CreatorID: uuid.UUID{}, CreatedAt: time.Time{}, UpdatedAt: time.Time{}, } @@ -371,7 +373,7 @@ func makeWip(ctx context.Context, wipRepo models.IWipRepo, repoID, refID uuid.UU BaseTree: parentHash, RefID: refID, RepositoryID: repoID, - CreateID: uuid.UUID{}, + CreatorID: uuid.UUID{}, CreatedAt: time.Time{}, UpdatedAt: time.Time{}, } From a46bed6a6e907753a4db6a88a64162758e5b803f Mon Sep 17 00:00:00 2001 From: taoshengshi Date: Wed, 13 Dec 2023 12:44:13 +0800 Subject: [PATCH 065/210] ignore .DS_Store --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c1de8634..9f63b47b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ jzfs # goland *.idea +*.DS_Store # Test binary, built with `go test -c` *.test From 04288e04d91342a0bef38f65ac00b27ea8346d1c Mon Sep 17 00:00:00 2001 From: taoshengshi Date: Wed, 13 Dec 2023 12:44:40 +0800 Subject: [PATCH 066/210] research on versioning Machine Learning paper --- docs/img/model.jpg | Bin 0 -> 74533 bytes docs/img/system.jpg | Bin 0 -> 345800 bytes docs/img/veml.jpg | Bin 0 -> 71713 bytes docs/veml.md | 370 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 370 insertions(+) create mode 100644 docs/img/model.jpg create mode 100644 docs/img/system.jpg create mode 100644 docs/img/veml.jpg create mode 100644 docs/veml.md diff --git a/docs/img/model.jpg b/docs/img/model.jpg new file mode 100644 index 0000000000000000000000000000000000000000..63ee3fe3695c005b27785c906944cb3fb1b2838e GIT binary patch literal 74533 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 literal 0 HcmV?d00001 diff --git a/docs/img/system.jpg b/docs/img/system.jpg new file mode 100644 index 0000000000000000000000000000000000000000..defe7a1077af57703e51c461373439f4785dcea8 GIT binary patch literal 345800 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

o{n}4qe-~-l3YtRui&@Jxq2a=}xq3u46cn34=Nx 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%0DGWh3S>cjXga@j8}M!uU>aZal~U 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 *XaLfIJJ7jz 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

v6X8&|919%RKF2CXS^)B^q{8ZG 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 R_ Date: Wed, 13 Dec 2023 09:25:51 +0800 Subject: [PATCH 067/210] feat: add mr model --- models/merge_request.go | 78 +++++++++++++++++++ models/merge_request_test.go | 34 ++++++++ .../migrations/20210505110026_init_project.go | 6 ++ models/object.go | 10 +-- models/ref.go | 4 +- models/repository.go | 4 +- models/wip.go | 4 +- 7 files changed, 129 insertions(+), 11 deletions(-) create mode 100644 models/merge_request.go create mode 100644 models/merge_request_test.go diff --git a/models/merge_request.go b/models/merge_request.go new file mode 100644 index 00000000..7cb020c3 --- /dev/null +++ b/models/merge_request.go @@ -0,0 +1,78 @@ +package models + +import ( + "context" + "time" + + "github.com/google/uuid" + "github.com/uptrace/bun" +) + +type MergeStatus int + +type MergeRequest struct { + bun.BaseModel `bun:"table:merge_requests"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` + TargetBranch string `bun:"target_branch,notnull"` + SourceBranch string `bun:"source_branch,notnull"` + SourceRepoID uuid.UUID `bun:"source_repo_id,type:bytea,notnull"` + TargetRepoID uuid.UUID `bun:"target_repo_id,type:bytea,notnull"` + Title string `bun:"title,notnull"` + MergeStatus MergeStatus `bun:"merge_status,notnull"` + Description *string `bun:"description"` + + AuthorID uuid.UUID `bun:"author_id,type:bytea,notnull"` + AssigneeID uuid.UUID `bun:"assignee_id,type:bytea"` + MergeUserID uuid.UUID `bun:"merge_user_id,type:bytea"` + + ApprovalsBeforeMerge int `bun:"approvals_before_merge"` + CreatedAt time.Time `bun:"created_at"` + UpdatedAt time.Time `bun:"updated_at"` +} + +type GetMergeRequestParams struct { + ID uuid.UUID +} + +func NewGetMergeRequestParams() *GetMergeRequestParams { + return &GetMergeRequestParams{} +} + +func (gmr *GetMergeRequestParams) SetID(id uuid.UUID) *GetMergeRequestParams { + gmr.ID = id + return gmr +} + +type IMergeRequestRepo interface { + Insert(ctx context.Context, ref *MergeRequest) (*MergeRequest, error) + Get(ctx context.Context, params *GetMergeRequestParams) (*MergeRequest, error) +} + +var _ IMergeRequestRepo = (*MergeRequestRepo)(nil) + +type MergeRequestRepo struct { + db bun.IDB +} + +func NewMergeRequestRepo(db bun.IDB) IMergeRequestRepo { + return &MergeRequestRepo{db: db} +} + +func (m MergeRequestRepo) Insert(ctx context.Context, mr *MergeRequest) (*MergeRequest, error) { + _, err := m.db.NewInsert().Model(mr).Exec(ctx) + if err != nil { + return nil, err + } + return mr, nil +} + +func (m MergeRequestRepo) Get(ctx context.Context, params *GetMergeRequestParams) (*MergeRequest, error) { + mergeRequest := &MergeRequest{} + query := m.db.NewSelect().Model(mergeRequest) + + if uuid.Nil != params.ID { + query = query.Where("id = ?", params.ID) + } + + return mergeRequest, query.Limit(1).Scan(ctx, mergeRequest) +} diff --git a/models/merge_request_test.go b/models/merge_request_test.go new file mode 100644 index 00000000..23b1da43 --- /dev/null +++ b/models/merge_request_test.go @@ -0,0 +1,34 @@ +package models_test + +import ( + "context" + "testing" + + "github.com/brianvoe/gofakeit/v6" + "github.com/google/go-cmp/cmp" + "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/stretchr/testify/require" +) + +func TestMergeRequestRepoInsert(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + mrRepo := models.NewMergeRequestRepo(db) + + mrModel := &models.MergeRequest{} + require.NoError(t, gofakeit.Struct(mrModel)) + newMrModel, err := mrRepo.Insert(ctx, mrModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newMrModel.ID) + + getMRParams := models.NewGetMergeRequestParams(). + SetID(newMrModel.ID) + mrModel, err = mrRepo.Get(ctx, getMRParams) + require.NoError(t, err) + + require.True(t, cmp.Equal(mrModel, newMrModel, dbTimeCmpOpt)) +} diff --git a/models/migrations/20210505110026_init_project.go b/models/migrations/20210505110026_init_project.go index 24c8fe39..470a9ca5 100644 --- a/models/migrations/20210505110026_init_project.go +++ b/models/migrations/20210505110026_init_project.go @@ -53,6 +53,12 @@ func init() { if err != nil { return err } + _, err = db.NewCreateTable(). + Model((*models.MergeRequest)(nil)). + Exec(ctx) + if err != nil { + return err + } //object _, err = db.NewCreateTable(). Model((*models.Object)(nil)). diff --git a/models/object.go b/models/object.go index 38f337d6..ed513659 100644 --- a/models/object.go +++ b/models/object.go @@ -51,7 +51,7 @@ func (treeEntry TreeEntry) Equal(other TreeEntry) bool { } type Blob struct { - bun.BaseModel `bun:"table:object"` + bun.BaseModel `bun:"table:objects"` Hash hash.Hash `bun:"hash,pk,type:bytea"` Type ObjectType `bun:"type"` Size int64 `bun:"size"` @@ -71,7 +71,7 @@ func (blob *Blob) Object() *Object { } type TreeNode struct { - bun.BaseModel `bun:"table:object"` + bun.BaseModel `bun:"table:objects"` Hash hash.Hash `bun:"hash,pk,type:bytea"` Type ObjectType `bun:"type"` SubObjects []TreeEntry `bun:"subObjs,type:jsonb"` @@ -130,7 +130,7 @@ func (tn *TreeNode) GetHash() (hash.Hash, error) { } type Commit struct { - bun.BaseModel `bun:"table:object"` + bun.BaseModel `bun:"table:objects"` Hash hash.Hash `bun:"hash,pk,type:bytea"` Type ObjectType `bun:"type"` //////********commit********//////// @@ -234,7 +234,7 @@ func (commit *Commit) Object() *Object { } type Tag struct { - bun.BaseModel `bun:"table:object"` + bun.BaseModel `bun:"table:objects"` Hash hash.Hash `bun:"hash,pk,type:bytea"` Type ObjectType `bun:"type"` //////********commit********//////// @@ -268,7 +268,7 @@ func (tag *Tag) Object() *Object { } type Object struct { - bun.BaseModel `bun:"table:object"` + bun.BaseModel `bun:"table:objects"` Hash hash.Hash `bun:"hash,pk,type:bytea"` Type ObjectType `bun:"type"` Size int64 `bun:"size"` diff --git a/models/ref.go b/models/ref.go index 141aeb7b..a24f3f63 100644 --- a/models/ref.go +++ b/models/ref.go @@ -11,7 +11,7 @@ import ( ) type Ref struct { - bun.BaseModel `bun:"table:ref"` + bun.BaseModel `bun:"table:refs"` ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` // RepositoryId which repository this branch belong RepositoryID uuid.UUID `bun:"repository_id,type:uuid,notnull"` @@ -53,7 +53,7 @@ func (gup *GetRefParams) SetName(name string) *GetRefParams { } type UpdateRefParams struct { - bun.BaseModel `bun:"table:ref"` + bun.BaseModel `bun:"table:refs"` ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` CommitHash hash.Hash `bun:"commit_hash,type:bytea,notnull"` } diff --git a/models/repository.go b/models/repository.go index 470c3374..df3412dc 100644 --- a/models/repository.go +++ b/models/repository.go @@ -9,7 +9,7 @@ import ( ) type Repository struct { - bun.BaseModel `bun:"table:repository"` + bun.BaseModel `bun:"table:repositories"` ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` Name string `bun:"name,notnull"` Description *string `bun:"description"` @@ -78,7 +78,7 @@ func (drp *DeleteRepoParams) SetID(id uuid.UUID) *DeleteRepoParams { } type UpdateRepoParams struct { - bun.BaseModel `bun:"table:repository"` + bun.BaseModel `bun:"table:repositories"` ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` Description *string `bun:"description"` } diff --git a/models/wip.go b/models/wip.go index 4854ef70..f1eb51c6 100644 --- a/models/wip.go +++ b/models/wip.go @@ -17,7 +17,7 @@ const ( ) type WorkingInProcess struct { - bun.BaseModel `bun:"table:wip"` + bun.BaseModel `bun:"table:wips"` ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` Name string `bun:"name,notnull"` CurrentTree hash.Hash `bun:"current_tree,type:bytea,notnull"` @@ -117,7 +117,7 @@ func (dwp *DeleteWipParams) SetRefID(refID uuid.UUID) *DeleteWipParams { } type UpdateWipParams struct { - bun.BaseModel `bun:"table:wip"` + bun.BaseModel `bun:"table:wips"` ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` CurrentTree hash.Hash `bun:"current_tree,type:bytea,notnull"` BaseTree hash.Hash `bun:"base_tree,type:bytea,notnull"` From d23866e8fb5e5ae0e27624661483e7cf7b268433 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Wed, 13 Dec 2023 21:00:49 +0800 Subject: [PATCH 068/210] feat: change url and add cookie auth --- auth/auth_middleware.go | 396 ++++++++++++++++++++++++++++++ auth/authenticator.go | 13 + auth/context.go | 25 ++ auth/crypt/encryption.go | 103 ++++++++ auth/crypt/encryption_test.go | 56 +++++ auth/session_store.go | 1 + auth/token.go | 21 ++ versionmgr/commit_walker_ctime.go | 103 ++++++++ 8 files changed, 718 insertions(+) create mode 100644 auth/auth_middleware.go create mode 100644 auth/authenticator.go create mode 100644 auth/context.go create mode 100644 auth/crypt/encryption.go create mode 100644 auth/crypt/encryption_test.go create mode 100644 auth/session_store.go create mode 100644 auth/token.go create mode 100644 versionmgr/commit_walker_ctime.go diff --git a/auth/auth_middleware.go b/auth/auth_middleware.go new file mode 100644 index 00000000..3b4b27cf --- /dev/null +++ b/auth/auth_middleware.go @@ -0,0 +1,396 @@ +package api + +import ( + "context" + "errors" + "fmt" + "net/http" + "strings" + "time" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/routers" + "github.com/getkin/kin-openapi/routers/legacy" + "github.com/gorilla/sessions" + "github.com/treeverse/lakefs/pkg/api/apigen" + "github.com/treeverse/lakefs/pkg/auth" + "github.com/treeverse/lakefs/pkg/auth/model" + oidcencoding "github.com/treeverse/lakefs/pkg/auth/oidc/encoding" + "github.com/treeverse/lakefs/pkg/logging" +) + +const ( + TokenSessionKeyName = "token" + InternalAuthSessionName = "internal_auth_session" + IDTokenClaimsSessionKey = "id_token_claims" + OIDCAuthSessionName = "oidc_auth_session" + SAMLTokenClaimsSessionKey = "saml_token_claims" + SAMLAuthSessionName = "saml_auth_session" +) + +// extractSecurityRequirements using Swagger returns an array of security requirements set for the request. +func extractSecurityRequirements(router routers.Router, r *http.Request) (openapi3.SecurityRequirements, error) { + // Find route + route, _, err := router.FindRoute(r) + if err != nil { + return nil, err + } + if route.Operation.Security == nil { + return route.Swagger.Security, nil + } + return *route.Operation.Security, nil +} + +type OIDCConfig struct { + ValidateIDTokenClaims map[string]string + DefaultInitialGroups []string + InitialGroupsClaimName string + FriendlyNameClaimName string +} + +type CookieAuthConfig struct { + ValidateIDTokenClaims map[string]string + DefaultInitialGroups []string + InitialGroupsClaimName string + FriendlyNameClaimName string + ExternalUserIDClaimName string + AuthSource string +} + +func GenericAuthMiddleware(logger logging.Logger, authenticator auth.Authenticator, authService auth.Service, oidcConfig *OIDCConfig, cookieAuthConfig *CookieAuthConfig) (func(next http.Handler) http.Handler, error) { + swagger, err := apigen.GetSwagger() + if err != nil { + return nil, err + } + sessionStore := sessions.NewCookieStore(authService.SecretStore().SharedSecret()) + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + user, err := checkSecurityRequirements(r, swagger.Security, logger, authenticator, authService, sessionStore, oidcConfig, cookieAuthConfig) + if err != nil { + writeError(w, r, http.StatusUnauthorized, err) + return + } + if user != nil { + ctx := logging.AddFields(r.Context(), logging.Fields{logging.UserFieldKey: user.Username}) + r = r.WithContext(auth.WithUser(ctx, user)) + } + next.ServeHTTP(w, r) + }) + }, nil +} + +func AuthMiddleware(logger logging.Logger, swagger *openapi3.Swagger, authenticator auth.Authenticator, authService auth.Service, sessionStore sessions.Store, oidcConfig *OIDCConfig, cookieAuthConfig *CookieAuthConfig) func(next http.Handler) http.Handler { + router, err := legacy.NewRouter(swagger) + if err != nil { + panic(err) + } + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // if request already authenticated + if _, userNotFoundErr := auth.GetUser(r.Context()); userNotFoundErr == nil { + next.ServeHTTP(w, r) + return + } + securityRequirements, err := extractSecurityRequirements(router, r) + if err != nil { + writeError(w, r, http.StatusBadRequest, err) + return + } + user, err := checkSecurityRequirements(r, securityRequirements, logger, authenticator, authService, sessionStore, oidcConfig, cookieAuthConfig) + if err != nil { + writeError(w, r, http.StatusUnauthorized, err) + return + } + if user != nil { + ctx := logging.AddFields(r.Context(), logging.Fields{logging.UserFieldKey: user.Username}) + r = r.WithContext(auth.WithUser(ctx, user)) + } + next.ServeHTTP(w, r) + }) + } +} + +// checkSecurityRequirements goes over the security requirements and check the authentication. returns the user information and error if the security check was required. +// it will return nil user and error in case of no security checks to match. +func checkSecurityRequirements(r *http.Request, + securityRequirements openapi3.SecurityRequirements, + logger logging.Logger, + authenticator auth.Authenticator, + authService auth.Service, + sessionStore sessions.Store, + oidcConfig *OIDCConfig, + cookieAuthConfig *CookieAuthConfig, +) (*model.User, error) { + ctx := r.Context() + var user *model.User + var err error + + logger = logger.WithContext(ctx) + + for _, securityRequirement := range securityRequirements { + for provider := range securityRequirement { + switch provider { + case "jwt_token": + // validate jwt token from header + authHeaderValue := r.Header.Get("Authorization") + if authHeaderValue == "" { + continue + } + parts := strings.Fields(authHeaderValue) + if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") { + continue + } + token := parts[1] + user, err = userByToken(ctx, logger, authService, token) + case "basic_auth": + // validate using basic auth + accessKey, secretKey, ok := r.BasicAuth() + if !ok { + continue + } + user, err = userByAuth(ctx, logger, authenticator, authService, accessKey, secretKey) + case "cookie_auth": + var internalAuthSession *sessions.Session + internalAuthSession, _ = sessionStore.Get(r, InternalAuthSessionName) + token := "" + if internalAuthSession != nil { + token, _ = internalAuthSession.Values[TokenSessionKeyName].(string) + } + if token == "" { + continue + } + user, err = userByToken(ctx, logger, authService, token) + case "oidc_auth": + var oidcSession *sessions.Session + oidcSession, err = sessionStore.Get(r, OIDCAuthSessionName) + if err != nil { + return nil, err + } + user, err = userFromOIDC(ctx, logger, authService, oidcSession, oidcConfig) + case "saml_auth": + var samlSession *sessions.Session + samlSession, err = sessionStore.Get(r, SAMLAuthSessionName) + if err != nil { + return nil, err + } + user, err = userFromSAML(ctx, logger, authService, samlSession, cookieAuthConfig) + default: + // unknown security requirement to check + logger.WithField("provider", provider).Error("Authentication middleware unknown security requirement provider") + return nil, ErrAuthenticatingRequest + } + + if err != nil { + return nil, err + } + if user != nil { + return user, nil + } + } + } + return nil, nil +} + +func enhanceWithFriendlyName(user *model.User, friendlyName string) *model.User { + if friendlyName != "" { + user.FriendlyName = &friendlyName + } + return user +} + +// userFromSAML returns a user from an existing SAML session. +// If the user doesn't exist on the lakeFS side, it is created. +// This function does not make any calls to an external provider. +func userFromSAML(ctx context.Context, logger logging.Logger, authService auth.Service, authSession *sessions.Session, cookieAuthConfig *CookieAuthConfig) (*model.User, error) { + idTokenClaims, ok := authSession.Values[SAMLTokenClaimsSessionKey].(oidcencoding.Claims) + if idTokenClaims == nil { + return nil, nil + } + if !ok { + logger.WithField("claims", authSession.Values[SAMLTokenClaimsSessionKey]).Debug("failed decoding tokens") + return nil, fmt.Errorf("getting token claims: %w", ErrAuthenticatingRequest) + } + logger.WithField("claims", idTokenClaims).Debug("Success decoding token claims") + + idKey := cookieAuthConfig.ExternalUserIDClaimName + externalID, ok := idTokenClaims[idKey].(string) + if !ok { + logger.WithField(idKey, idTokenClaims[idKey]).Error("Failed type assertion for sub claim") + return nil, ErrAuthenticatingRequest + } + + log := logger.WithField("external_id", externalID) + + for claimName, expectedValue := range cookieAuthConfig.ValidateIDTokenClaims { + actualValue, ok := idTokenClaims[claimName] + if !ok || actualValue != expectedValue { + log.WithFields(logging.Fields{ + "claim_name": claimName, + "actual_value": actualValue, + "expected_value": expectedValue, + "missing": !ok, + }).Error("authentication failed on validating ID token claims") + return nil, ErrAuthenticatingRequest + } + } + + // update user + // TODO(isan) consolidate userFromOIDC and userFromSAML below here internal db handling code + friendlyName := "" + if cookieAuthConfig.FriendlyNameClaimName != "" { + friendlyName, _ = idTokenClaims[cookieAuthConfig.FriendlyNameClaimName].(string) + } + log = log.WithField("friendly_name", friendlyName) + + user, err := authService.GetUserByExternalID(ctx, externalID) + if err == nil { + log.Info("Found user") + return enhanceWithFriendlyName(user, friendlyName), nil + } + if !errors.Is(err, auth.ErrNotFound) { + log.WithError(err).Error("Failed while searching if user exists in database") + return nil, ErrAuthenticatingRequest + } + log.Info("User not found; creating them") + + u := model.User{ + CreatedAt: time.Now().UTC(), + Source: cookieAuthConfig.AuthSource, + Username: externalID, + ExternalID: &externalID, + } + + _, err = authService.CreateUser(ctx, &u) + if err != nil { + if !errors.Is(err, auth.ErrAlreadyExists) { + log.WithError(err).Error("Failed to create external user in database") + return nil, ErrAuthenticatingRequest + } + // user already exists - get it: + user, err = authService.GetUserByExternalID(ctx, externalID) + if err != nil { + log.WithError(err).Error("failed to get external user from database") + return nil, ErrAuthenticatingRequest + } + return enhanceWithFriendlyName(user, friendlyName), nil + } + initialGroups := cookieAuthConfig.DefaultInitialGroups + if userInitialGroups, ok := idTokenClaims[cookieAuthConfig.InitialGroupsClaimName].(string); ok { + initialGroups = strings.Split(userInitialGroups, ",") + } + for _, g := range initialGroups { + err = authService.AddUserToGroup(ctx, u.Username, strings.TrimSpace(g)) + if err != nil { + log.WithError(err).Error("Failed to add external user to group") + } + } + return enhanceWithFriendlyName(&u, friendlyName), nil +} + +// userFromOIDC returns a user from an existing OIDC session. +// If the user doesn't exist on the lakeFS side, it is created. +// This function does not make any calls to an external provider. +func userFromOIDC(ctx context.Context, logger logging.Logger, authService auth.Service, authSession *sessions.Session, oidcConfig *OIDCConfig) (*model.User, error) { + idTokenClaims, ok := authSession.Values[IDTokenClaimsSessionKey].(oidcencoding.Claims) + if idTokenClaims == nil { + return nil, nil + } + if !ok || idTokenClaims == nil { + return nil, ErrAuthenticatingRequest + } + externalID, ok := idTokenClaims["sub"].(string) + if !ok { + logger.WithField("sub", idTokenClaims["sub"]).Error("Failed type assertion for sub claim") + return nil, ErrAuthenticatingRequest + } + for claimName, expectedValue := range oidcConfig.ValidateIDTokenClaims { + actualValue, ok := idTokenClaims[claimName] + if !ok || actualValue != expectedValue { + logger.WithFields(logging.Fields{ + "claim_name": claimName, + "actual_value": actualValue, + "expected_value": expectedValue, + "missing": !ok, + }).Error("Authentication failed on validating ID token claims") + return nil, ErrAuthenticatingRequest + } + } + friendlyName := "" + if oidcConfig.FriendlyNameClaimName != "" { + friendlyName, _ = idTokenClaims[oidcConfig.FriendlyNameClaimName].(string) + } + user, err := authService.GetUserByExternalID(ctx, externalID) + if err == nil { + return enhanceWithFriendlyName(user, friendlyName), nil + } + if !errors.Is(err, auth.ErrNotFound) { + logger.WithError(err).Error("Failed to get external user from database") + return nil, ErrAuthenticatingRequest + } + u := model.User{ + CreatedAt: time.Now().UTC(), + Source: "oidc", + Username: externalID, + ExternalID: &externalID, + } + _, err = authService.CreateUser(ctx, &u) + if err != nil { + if !errors.Is(err, auth.ErrAlreadyExists) { + logger.WithError(err).Error("Failed to create external user in database") + return nil, ErrAuthenticatingRequest + } + // user already exists - get it: + user, err = authService.GetUserByExternalID(ctx, externalID) + if err != nil { + logger.WithError(err).Error("Failed to get external user from database") + return nil, ErrAuthenticatingRequest + } + return enhanceWithFriendlyName(user, friendlyName), nil + } + initialGroups := oidcConfig.DefaultInitialGroups + if userInitialGroups, ok := idTokenClaims[oidcConfig.InitialGroupsClaimName].(string); ok { + initialGroups = strings.Split(userInitialGroups, ",") + } + for _, g := range initialGroups { + err = authService.AddUserToGroup(ctx, u.Username, strings.TrimSpace(g)) + if err != nil { + logger.WithError(err).Error("Failed to add external user to group") + } + } + return enhanceWithFriendlyName(&u, friendlyName), nil +} + +func userByToken(ctx context.Context, logger logging.Logger, authService auth.Service, tokenString string) (*model.User, error) { + claims, err := auth.VerifyToken(authService.SecretStore().SharedSecret(), tokenString) + // make sure no audience is set for login token + if err != nil || !claims.VerifyAudience(LoginAudience, false) { + return nil, ErrAuthenticatingRequest + } + + username := claims.Subject + userData, err := authService.GetUser(ctx, username) + if err != nil { + logger.WithFields(logging.Fields{ + "token_id": claims.Id, + "username": username, + "subject": claims.Subject, + }).Debug("could not find user id by credentials") + return nil, ErrAuthenticatingRequest + } + return userData, nil +} + +func userByAuth(ctx context.Context, logger logging.Logger, authenticator auth.Authenticator, authService auth.Service, accessKey string, secretKey string) (*model.User, error) { + // TODO(ariels): Rename keys. + username, err := authenticator.AuthenticateUser(ctx, accessKey, secretKey) + if err != nil { + logger.WithError(err).WithField("user", accessKey).Error("authenticate") + return nil, ErrAuthenticatingRequest + } + user, err := authService.GetUser(ctx, username) + if err != nil { + logger.WithError(err).WithFields(logging.Fields{"user_name": username}).Debug("could not find user id by credentials") + return nil, ErrAuthenticatingRequest + } + return user, nil +} diff --git a/auth/authenticator.go b/auth/authenticator.go new file mode 100644 index 00000000..5f7b421f --- /dev/null +++ b/auth/authenticator.go @@ -0,0 +1,13 @@ +package auth + +import "context" + +// Authenticator authenticates users returning an identifier for the user. +// (Currently it handles only username+password single-step authentication. +// This interface will need to change significantly in order to support +// challenge-response protocols.) +type Authenticator interface { + // AuthenticateUser authenticates a user matching username and + // password and returns their ID. + AuthenticateUser(ctx context.Context, username, password string) (string, error) +} diff --git a/auth/context.go b/auth/context.go new file mode 100644 index 00000000..a2148f31 --- /dev/null +++ b/auth/context.go @@ -0,0 +1,25 @@ +package auth + +import ( + "context" + + "github.com/treeverse/lakefs/pkg/auth/model" +) + +type contextKey string + +const ( + userContextKey contextKey = "user" +) + +func GetUser(ctx context.Context) (*model.User, error) { + user, ok := ctx.Value(userContextKey).(*model.User) + if !ok { + return nil, ErrUserNotFound + } + return user, nil +} + +func WithUser(ctx context.Context, user *model.User) context.Context { + return context.WithValue(ctx, userContextKey, user) +} diff --git a/auth/crypt/encryption.go b/auth/crypt/encryption.go new file mode 100644 index 00000000..2d858975 --- /dev/null +++ b/auth/crypt/encryption.go @@ -0,0 +1,103 @@ +package crypt + +import ( + "crypto/rand" + "errors" + "io" + + "golang.org/x/crypto/nacl/secretbox" + "golang.org/x/crypto/scrypt" +) + +const ( + KeySaltBytes = 8 + KeySizeBytes = 32 + NonceSizeBytes = 24 +) + +type SecretStore interface { + SharedSecret() []byte + Encrypt(data []byte) ([]byte, error) + Decrypt(encrypted []byte) ([]byte, error) +} + +type NaclSecretStore struct { + secret []byte +} + +var ( + ErrFailDecrypt = errors.New("could not decrypt value") +) + +func NewSecretStore(secret []byte) *NaclSecretStore { + return &NaclSecretStore{secret: secret} +} + +func (a *NaclSecretStore) SharedSecret() []byte { + return a.secret +} + +func (a *NaclSecretStore) kdf(storedSalt []byte) (key [KeySizeBytes]byte, salt [KeySaltBytes]byte, err error) { + if storedSalt != nil { + copy(salt[:], storedSalt) + } else if _, err = io.ReadFull(rand.Reader, salt[:]); err != nil { + return + } + // scrypt's N, r & p, benchmarked to run at about 1ms, since it's in the critical path. + // fair trade-off for a high throughput low latency system + const ( + N = 512 + r = 8 + p = 1 + keyLen = 32 + ) + keySlice, err := scrypt.Key(a.secret, salt[:], N, r, p, keyLen) + if err != nil { + return + } + copy(key[:], keySlice) + return +} + +func (a *NaclSecretStore) Encrypt(data []byte) ([]byte, error) { + // generate a random nonce + var nonce [NonceSizeBytes]byte + if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil { + return nil, err + } + + // derive a key from the stored secret, generating a new random salt + key, salt, err := a.kdf(nil) + if err != nil { + return nil, err + } + + // use nonce and derived key to encrypt data + encrypted := secretbox.Seal(nonce[:], data, &nonce, &key) + + // kdf salt (8) + nonce (24) + encrypted data (rest) + return append(salt[:], encrypted...), nil +} + +func (a *NaclSecretStore) Decrypt(encrypted []byte) ([]byte, error) { + // extract salt + var salt [KeySaltBytes]byte + copy(salt[:], encrypted[:KeySaltBytes]) + + // derive encryption key from salt and stored secret + key, _, err := a.kdf(salt[:]) + if err != nil { + return nil, err + } + + // extract nonce + var decryptNonce [NonceSizeBytes]byte + copy(decryptNonce[:], encrypted[KeySaltBytes:KeySaltBytes+NonceSizeBytes]) + + // decrypt the rest + decrypted, ok := secretbox.Open(nil, encrypted[KeySaltBytes+NonceSizeBytes:], &decryptNonce, &key) + if !ok { + return nil, ErrFailDecrypt + } + return decrypted, nil +} diff --git a/auth/crypt/encryption_test.go b/auth/crypt/encryption_test.go new file mode 100644 index 00000000..de7adbce --- /dev/null +++ b/auth/crypt/encryption_test.go @@ -0,0 +1,56 @@ +package crypt_test + +import ( + "bytes" + "fmt" + "testing" + + "github.com/treeverse/lakefs/pkg/auth/crypt" +) + +func TestSecretStore_Encrypt(t *testing.T) { + cases := []struct { + Secret string + Data []byte + }{ + {"some secret", []byte("test string")}, + {"some other secret with a different length", []byte("another test string")}, + {"", []byte("something else, empty secret")}, + {"1", []byte("short secret this time")}, + {"some secret", []byte("")}, + } + + for i, cas := range cases { + t.Run(fmt.Sprintf("encrypt_%d", i), func(t *testing.T) { + aes := crypt.NewSecretStore([]byte(cas.Secret)) + encrypted, err := aes.Encrypt(cas.Data) + if err != nil { + t.Fatal(err) + } + + decrypted, err := aes.Decrypt(encrypted) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(cas.Data, decrypted) { + t.Fatalf("expected decrypted data to equal original data %s, instead got %s", cas.Data, decrypted) + } + + }) + } +} + +func BenchmarkSecretStore_Encrypt(b *testing.B) { + secret := "foo bar" + data := []byte("some value, it doesn't really matter what") + aes := crypt.NewSecretStore([]byte(secret)) + encrypted, err := aes.Encrypt(data) + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = aes.Decrypt(encrypted) + } +} diff --git a/auth/session_store.go b/auth/session_store.go new file mode 100644 index 00000000..8832b06d --- /dev/null +++ b/auth/session_store.go @@ -0,0 +1 @@ +package auth diff --git a/auth/token.go b/auth/token.go new file mode 100644 index 00000000..93881e20 --- /dev/null +++ b/auth/token.go @@ -0,0 +1,21 @@ +package auth + +import ( + "fmt" + + "github.com/golang-jwt/jwt" +) + +func VerifyToken(secret []byte, tokenString string) (*jwt.StandardClaims, error) { + claims := &jwt.StandardClaims{} + token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("%w: %s", ErrUnexpectedSigningMethod, token.Header["alg"]) + } + return secret, nil + }) + if err != nil || !token.Valid { + return nil, ErrInvalidToken + } + return claims, nil +} diff --git a/versionmgr/commit_walker_ctime.go b/versionmgr/commit_walker_ctime.go new file mode 100644 index 00000000..fbddf1d2 --- /dev/null +++ b/versionmgr/commit_walker_ctime.go @@ -0,0 +1,103 @@ +package object + +import ( + "io" + + "github.com/emirpasic/gods/trees/binaryheap" + + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/storer" +) + +type commitIteratorByCTime struct { + seenExternal map[plumbing.Hash]bool + seen map[plumbing.Hash]bool + heap *binaryheap.Heap +} + +// NewCommitIterCTime returns a CommitIter that walks the commit history, +// starting at the given commit and visiting its parents while preserving Committer Time order. +// this appears to be the closest order to `git log` +// The given callback will be called for each visited commit. Each commit will +// be visited only once. If the callback returns an error, walking will stop +// and will return the error. Other errors might be returned if the history +// cannot be traversed (e.g. missing objects). Ignore allows to skip some +// commits from being iterated. +func NewCommitIterCTime( + c *Commit, + seenExternal map[plumbing.Hash]bool, + ignore []plumbing.Hash, +) CommitIter { + seen := make(map[plumbing.Hash]bool) + for _, h := range ignore { + seen[h] = true + } + + heap := binaryheap.NewWith(func(a, b interface{}) int { + if a.(*Commit).Committer.When.Before(b.(*Commit).Committer.When) { + return 1 + } + return -1 + }) + heap.Push(c) + + return &commitIteratorByCTime{ + seenExternal: seenExternal, + seen: seen, + heap: heap, + } +} + +func (w *commitIteratorByCTime) Next() (*Commit, error) { + var c *Commit + for { + cIn, ok := w.heap.Pop() + if !ok { + return nil, io.EOF + } + c = cIn.(*Commit) + + if w.seen[c.Hash] || w.seenExternal[c.Hash] { + continue + } + + w.seen[c.Hash] = true + + for _, h := range c.ParentHashes { + if w.seen[h] || w.seenExternal[h] { + continue + } + pc, err := GetCommit(c.s, h) + if err != nil { + return nil, err + } + w.heap.Push(pc) + } + + return c, nil + } +} + +func (w *commitIteratorByCTime) ForEach(cb func(*Commit) error) error { + for { + c, err := w.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + + err = cb(c) + if err == storer.ErrStop { + break + } + if err != nil { + return err + } + } + + return nil +} + +func (w *commitIteratorByCTime) Close() {} From 5f3adfa61238f6670cc0b8ca0904c3b515f7fbaf Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Wed, 13 Dec 2023 21:01:23 +0800 Subject: [PATCH 069/210] feat: change url and add cookie auth --- api/api_impl/server.go | 60 +- api/jiaozifs.gen.go | 3218 ++++++++++++++++------------- api/swagger.yml | 210 +- api/tmpls/chi/chi-middleware.tmpl | 6 + auth/auth_middleware.go | 309 +-- auth/auth_test.go | 2 +- auth/authenticator.go | 2 +- auth/basic_auth.go | 8 +- auth/context.go | 11 +- auth/crypt/encryption_test.go | 2 +- auth/session_store.go | 11 + auth/token.go | 5 + cmd/daemon.go | 5 + controller/commit_ctl.go | 26 +- controller/repository_ctl.go | 107 +- controller/user_ctl.go | 44 +- controller/wip_ctl.go | 56 +- go.mod | 12 +- go.sum | 22 +- models/merge_request.go | 11 +- models/repository.go | 2 +- models/repository_test.go | 2 +- utils/hash/hash.go | 20 + utils/hash/hash_test.go | 11 + versionmgr/commit_walker_ctime.go | 39 +- 25 files changed, 2313 insertions(+), 1888 deletions(-) diff --git a/api/api_impl/server.go b/api/api_impl/server.go index 2868d01f..08121950 100644 --- a/api/api_impl/server.go +++ b/api/api_impl/server.go @@ -6,11 +6,21 @@ import ( "net" "net/http" + "github.com/MadAppGang/httplog" + "github.com/rs/cors" + + "github.com/flowchartsman/swaggerui" + + "github.com/gorilla/sessions" + + "github.com/jiaozifs/jiaozifs/models" + + "github.com/jiaozifs/jiaozifs/auth" + "net/url" "github.com/getkin/kin-openapi/openapi3filter" "github.com/go-chi/chi/v5" - "github.com/go-chi/cors" logging "github.com/ipfs/go-log/v2" "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/config" @@ -22,7 +32,7 @@ var log = logging.Logger("rpc") const APIV1Prefix = "/api/v1" -func SetupAPI(lc fx.Lifecycle, apiConfig *config.APIConfig, controller APIController) error { +func SetupAPI(lc fx.Lifecycle, apiConfig *config.APIConfig, sessionStore sessions.Store, repo models.IRepo, controller APIController) error { swagger, err := api.GetSwagger() if err != nil { return err @@ -30,35 +40,41 @@ func SetupAPI(lc fx.Lifecycle, apiConfig *config.APIConfig, controller APIContro // This is how you set up a basic chi router r := chi.NewRouter() - + r.Use(httplog.LoggerWithName("http"), + cors.New(cors.Options{ + AllowedOrigins: []string{"*"}, + AllowedMethods: []string{ + http.MethodHead, + http.MethodGet, + http.MethodPost, + http.MethodPut, + http.MethodPatch, + http.MethodDelete, + }, + AllowedHeaders: []string{"*"}, + AllowCredentials: true, + }).Handler, + ) // Use our validation middleware to check all requests against the // OpenAPI schema. - r.Use( - cors.Handler(cors.Options{ - // Basic CORS - // for more ideas, see: https://developer.github.com/v3/#cross-origin-resource-sharing - - // AllowedOrigins: []string{"https://foo.com"}, // Use this to allow specific origin hosts - AllowedOrigins: []string{"https://*", "http://*"}, - // AllowOriginFunc: func(r *http.Request, origin string) bool { return true }, - AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, - AllowedHeaders: []string{"*"}, - ExposedHeaders: []string{"*"}, - AllowCredentials: false, - MaxAge: 300, // Maximum value not ignored by any of major browsers - }), - + apiRouter := r.With( middleware.OapiRequestValidatorWithOptions(swagger, &middleware.Options{ Options: openapi3filter.Options{ - AuthenticationFunc: func(ctx context.Context, input *openapi3filter.AuthenticationInput) error { - return nil - }, + AuthenticationFunc: openapi3filter.NoopAuthenticationFunc, }, SilenceServersWarning: true, }), + auth.Middleware(swagger, nil, nil, repo.UserRepo(), sessionStore), ) - api.HandlerFromMuxWithBaseURL(controller, r, APIV1Prefix) + raw, err := api.RawSpec() + if err != nil { + return err + } + + api.HandlerFromMuxWithBaseURL(controller, apiRouter, APIV1Prefix) + r.Handle("/api/docs/*", http.StripPrefix("/api/docs", swaggerui.Handler(raw))) + url, err := url.Parse(apiConfig.Listen) if err != nil { return err diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 9ab5b129..ad346a6f 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -24,8 +24,9 @@ import ( ) const ( - Basic_authScopes = "basic_auth.Scopes" - Jwt_tokenScopes = "jwt_token.Scopes" + Basic_authScopes = "basic_auth.Scopes" + Cookie_authScopes = "cookie_auth.Scopes" + Jwt_tokenScopes = "jwt_token.Scopes" ) // AuthenticationToken defines model for AuthenticationToken. @@ -45,6 +46,26 @@ type Change struct { ToHash *string `json:"ToHash,omitempty"` } +// Commit defines model for Commit. +type Commit struct { + Author Signature `json:"Author"` + Committer Signature `json:"Committer"` + CreatedAt time.Time `json:"CreatedAt"` + Hash string `json:"Hash"` + MergeTag string `json:"MergeTag"` + Message string `json:"Message"` + ParentHashes []string `json:"ParentHashes"` + TreeHash string `json:"TreeHash"` + Type int8 `json:"Type"` + UpdatedAt time.Time `json:"UpdatedAt"` +} + +// CreateRepository defines model for CreateRepository. +type CreateRepository struct { + Description *string `json:"Description,omitempty"` + Name string `json:"Name"` +} + // ObjectStats defines model for ObjectStats. type ObjectStats struct { Checksum string `json:"checksum"` @@ -65,13 +86,20 @@ type ObjectUserMetadata map[string]string // Repository defines model for Repository. type Repository struct { - CreatedAt *time.Time `json:"CreatedAt,omitempty"` - CreatorID *openapi_types.UUID `json:"CreatorID,omitempty"` - Description *string `json:"Description,omitempty"` - Head *string `json:"Head,omitempty"` - ID *openapi_types.UUID `json:"ID,omitempty"` - Name *string `json:"Name,omitempty"` - UpdatedAt *time.Time `json:"UpdatedAt,omitempty"` + CreatedAt time.Time `json:"CreatedAt"` + CreatorID openapi_types.UUID `json:"CreatorID"` + Description *string `json:"Description,omitempty"` + Head string `json:"Head"` + ID openapi_types.UUID `json:"ID"` + Name string `json:"Name"` + UpdatedAt time.Time `json:"UpdatedAt"` +} + +// Signature defines model for Signature. +type Signature struct { + Email openapi_types.Email `json:"Email"` + Name string `json:"Name"` + When time.Time `json:"When"` } // TreeEntry defines model for TreeEntry. @@ -127,33 +155,6 @@ type Wip struct { UpdatedAt *time.Time `json:"UpdatedAt,omitempty"` } -// LoginJSONBody defines parameters for Login. -type LoginJSONBody struct { - Password string `json:"password"` - Username string `json:"username"` -} - -// CreateRepositoryParams defines parameters for CreateRepository. -type CreateRepositoryParams struct { - // Name repository name - Name string `form:"name" json:"name"` - - // Description repository description - Description *string `form:"description,omitempty" json:"description,omitempty"` -} - -// GetCommitDiffParams defines parameters for GetCommitDiff. -type GetCommitDiffParams struct { - // Path specific path, if not specific return entries in root - Path *string `form:"path,omitempty" json:"path,omitempty"` -} - -// GetEntriesInCommitParams defines parameters for GetEntriesInCommit. -type GetEntriesInCommitParams struct { - // Path specific path, if not specific return entries in root - Path *string `form:"path,omitempty" json:"path,omitempty"` -} - // DeleteObjectParams defines parameters for DeleteObject. type DeleteObjectParams struct { // WipID working in process @@ -212,10 +213,43 @@ type UploadObjectParams struct { IfNoneMatch *string `json:"If-None-Match,omitempty"` } -// CommitWipParams defines parameters for CommitWip. -type CommitWipParams struct { - // Msg commit message - Msg *string `form:"msg,omitempty" json:"msg,omitempty"` +// GetCommitsInRepositoryParams defines parameters for GetCommitsInRepository. +type GetCommitsInRepositoryParams struct { + // RefName ref(branch/tag) name + RefName *string `form:"refName,omitempty" json:"refName,omitempty"` +} + +// GetCommitDiffParams defines parameters for GetCommitDiff. +type GetCommitDiffParams struct { + // Path specific path, if not specific return entries in root + Path *string `form:"path,omitempty" json:"path,omitempty"` +} + +// GetEntriesInCommitParams defines parameters for GetEntriesInCommit. +type GetEntriesInCommitParams struct { + // Path specific path, if not specific return entries in root + Path *string `form:"path,omitempty" json:"path,omitempty"` + + // Ref specific ref + Ref *string `form:"ref,omitempty" json:"ref,omitempty"` +} + +// LoginJSONBody defines parameters for Login. +type LoginJSONBody struct { + Password string `json:"password"` + Username string `json:"username"` +} + +// DeleteWipParams defines parameters for DeleteWip. +type DeleteWipParams struct { + // RefName ref name + RefName string `form:"refName" json:"refName"` +} + +// GetWipParams defines parameters for GetWip. +type GetWipParams struct { + // RefName ref name + RefName string `form:"refName" json:"refName"` } // CreateWipParams defines parameters for CreateWip. @@ -225,19 +259,34 @@ type CreateWipParams struct { // BaseCommitID base commit to create wip BaseCommitID string `form:"baseCommitID" json:"baseCommitID"` + + // RefName ref name + RefName string `form:"refName" json:"refName"` +} + +// CommitWipParams defines parameters for CommitWip. +type CommitWipParams struct { + // Msg commit message + Msg *string `form:"msg,omitempty" json:"msg,omitempty"` + + // RefName ref name + RefName string `form:"refName" json:"refName"` } +// UploadObjectMultipartRequestBody defines body for UploadObject for multipart/form-data ContentType. +type UploadObjectMultipartRequestBody UploadObjectMultipartBody + +// UpdateRepositoryJSONRequestBody defines body for UpdateRepository for application/json ContentType. +type UpdateRepositoryJSONRequestBody = UpdateRepository + // LoginJSONRequestBody defines body for Login for application/json ContentType. type LoginJSONRequestBody LoginJSONBody // RegisterJSONRequestBody defines body for Register for application/json ContentType. type RegisterJSONRequestBody = UserRegisterInfo -// UpdateRepositoryJSONRequestBody defines body for UpdateRepository for application/json ContentType. -type UpdateRepositoryJSONRequestBody = UpdateRepository - -// UploadObjectMultipartRequestBody defines body for UploadObject for multipart/form-data ContentType. -type UploadObjectMultipartRequestBody UploadObjectMultipartBody +// CreateRepositoryJSONRequestBody defines body for CreateRepository for application/json ContentType. +type CreateRepositoryJSONRequestBody = CreateRepository // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error @@ -312,27 +361,17 @@ func WithRequestEditorFn(fn RequestEditorFn) ClientOption { // The interface specification for the client above. type ClientInterface interface { - // LoginWithBody request with any body - LoginWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - Login(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // RegisterWithBody request with any body - RegisterWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - Register(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // GetUserInfo request - GetUserInfo(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // DeleteObject request + DeleteObject(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) - // GetVersion request - GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetObject request + GetObject(ctx context.Context, user string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) - // ListRepository request - ListRepository(ctx context.Context, user string, reqEditors ...RequestEditorFn) (*http.Response, error) + // HeadObject request + HeadObject(ctx context.Context, user string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) - // CreateRepository request - CreateRepository(ctx context.Context, user string, params *CreateRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // UploadObjectWithBody request with any body + UploadObjectWithBody(ctx context.Context, user string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) // DeleteRepository request DeleteRepository(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -345,42 +384,60 @@ type ClientInterface interface { UpdateRepository(ctx context.Context, user string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetCommitsInRepository request + GetCommitsInRepository(ctx context.Context, user string, repository string, params *GetCommitsInRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetCommitDiff request - GetCommitDiff(ctx context.Context, user string, repository string, baseCommit string, toCommit string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*http.Response, error) + GetCommitDiff(ctx context.Context, user string, repository string, basehead string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetEntriesInCommit request - GetEntriesInCommit(ctx context.Context, user string, repository string, commitHash string, params *GetEntriesInCommitParams, reqEditors ...RequestEditorFn) (*http.Response, error) + GetEntriesInCommit(ctx context.Context, user string, repository string, params *GetEntriesInCommitParams, reqEditors ...RequestEditorFn) (*http.Response, error) - // DeleteObject request - DeleteObject(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // LoginWithBody request with any body + LoginWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - // GetObject request - GetObject(ctx context.Context, user string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) + Login(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // HeadObject request - HeadObject(ctx context.Context, user string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // Logout request + Logout(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - // UploadObjectWithBody request with any body - UploadObjectWithBody(ctx context.Context, user string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + // RegisterWithBody request with any body + RegisterWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - // CommitWip request - CommitWip(ctx context.Context, user string, repository string, refID openapi_types.UUID, params *CommitWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) + Register(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // ListWip request - ListWip(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) + // ListRepository request + ListRepository(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateRepositoryWithBody request with any body + CreateRepositoryWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateRepository(ctx context.Context, body CreateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetUserInfo request + GetUserInfo(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetVersion request + GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) // DeleteWip request - DeleteWip(ctx context.Context, user string, repository string, refID openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) + DeleteWip(ctx context.Context, repository string, params *DeleteWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetWip request - GetWip(ctx context.Context, user string, repository string, refID openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) + GetWip(ctx context.Context, repository string, params *GetWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) // CreateWip request - CreateWip(ctx context.Context, user string, repository string, refID openapi_types.UUID, params *CreateWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) + CreateWip(ctx context.Context, repository string, params *CreateWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CommitWip request + CommitWip(ctx context.Context, repository string, params *CommitWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // ListWip request + ListWip(ctx context.Context, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) } -func (c *Client) LoginWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewLoginRequestWithBody(c.Server, contentType, body) +func (c *Client) DeleteObject(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteObjectRequest(c.Server, user, repository, params) if err != nil { return nil, err } @@ -391,8 +448,8 @@ func (c *Client) LoginWithBody(ctx context.Context, contentType string, body io. return c.Client.Do(req) } -func (c *Client) Login(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewLoginRequest(c.Server, body) +func (c *Client) GetObject(ctx context.Context, user string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetObjectRequest(c.Server, user, repository, params) if err != nil { return nil, err } @@ -403,8 +460,8 @@ func (c *Client) Login(ctx context.Context, body LoginJSONRequestBody, reqEditor return c.Client.Do(req) } -func (c *Client) RegisterWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewRegisterRequestWithBody(c.Server, contentType, body) +func (c *Client) HeadObject(ctx context.Context, user string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewHeadObjectRequest(c.Server, user, repository, params) if err != nil { return nil, err } @@ -415,8 +472,8 @@ func (c *Client) RegisterWithBody(ctx context.Context, contentType string, body return c.Client.Do(req) } -func (c *Client) Register(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewRegisterRequest(c.Server, body) +func (c *Client) UploadObjectWithBody(ctx context.Context, user string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUploadObjectRequestWithBody(c.Server, user, repository, params, contentType, body) if err != nil { return nil, err } @@ -427,8 +484,8 @@ func (c *Client) Register(ctx context.Context, body RegisterJSONRequestBody, req return c.Client.Do(req) } -func (c *Client) GetUserInfo(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetUserInfoRequest(c.Server) +func (c *Client) DeleteRepository(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteRepositoryRequest(c.Server, user, repository) if err != nil { return nil, err } @@ -439,8 +496,8 @@ func (c *Client) GetUserInfo(ctx context.Context, reqEditors ...RequestEditorFn) return c.Client.Do(req) } -func (c *Client) GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetVersionRequest(c.Server) +func (c *Client) GetRepository(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetRepositoryRequest(c.Server, user, repository) if err != nil { return nil, err } @@ -451,8 +508,8 @@ func (c *Client) GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) return c.Client.Do(req) } -func (c *Client) ListRepository(ctx context.Context, user string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewListRepositoryRequest(c.Server, user) +func (c *Client) UpdateRepositoryWithBody(ctx context.Context, user string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateRepositoryRequestWithBody(c.Server, user, repository, contentType, body) if err != nil { return nil, err } @@ -463,8 +520,8 @@ func (c *Client) ListRepository(ctx context.Context, user string, reqEditors ... return c.Client.Do(req) } -func (c *Client) CreateRepository(ctx context.Context, user string, params *CreateRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateRepositoryRequest(c.Server, user, params) +func (c *Client) UpdateRepository(ctx context.Context, user string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateRepositoryRequest(c.Server, user, repository, body) if err != nil { return nil, err } @@ -475,8 +532,8 @@ func (c *Client) CreateRepository(ctx context.Context, user string, params *Crea return c.Client.Do(req) } -func (c *Client) DeleteRepository(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteRepositoryRequest(c.Server, user, repository) +func (c *Client) GetCommitsInRepository(ctx context.Context, user string, repository string, params *GetCommitsInRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetCommitsInRepositoryRequest(c.Server, user, repository, params) if err != nil { return nil, err } @@ -487,8 +544,8 @@ func (c *Client) DeleteRepository(ctx context.Context, user string, repository s return c.Client.Do(req) } -func (c *Client) GetRepository(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetRepositoryRequest(c.Server, user, repository) +func (c *Client) GetCommitDiff(ctx context.Context, user string, repository string, basehead string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetCommitDiffRequest(c.Server, user, repository, basehead, params) if err != nil { return nil, err } @@ -499,8 +556,8 @@ func (c *Client) GetRepository(ctx context.Context, user string, repository stri return c.Client.Do(req) } -func (c *Client) UpdateRepositoryWithBody(ctx context.Context, user string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewUpdateRepositoryRequestWithBody(c.Server, user, repository, contentType, body) +func (c *Client) GetEntriesInCommit(ctx context.Context, user string, repository string, params *GetEntriesInCommitParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetEntriesInCommitRequest(c.Server, user, repository, params) if err != nil { return nil, err } @@ -511,8 +568,8 @@ func (c *Client) UpdateRepositoryWithBody(ctx context.Context, user string, repo return c.Client.Do(req) } -func (c *Client) UpdateRepository(ctx context.Context, user string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewUpdateRepositoryRequest(c.Server, user, repository, body) +func (c *Client) LoginWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewLoginRequestWithBody(c.Server, contentType, body) if err != nil { return nil, err } @@ -523,8 +580,8 @@ func (c *Client) UpdateRepository(ctx context.Context, user string, repository s return c.Client.Do(req) } -func (c *Client) GetCommitDiff(ctx context.Context, user string, repository string, baseCommit string, toCommit string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetCommitDiffRequest(c.Server, user, repository, baseCommit, toCommit, params) +func (c *Client) Login(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewLoginRequest(c.Server, body) if err != nil { return nil, err } @@ -535,8 +592,8 @@ func (c *Client) GetCommitDiff(ctx context.Context, user string, repository stri return c.Client.Do(req) } -func (c *Client) GetEntriesInCommit(ctx context.Context, user string, repository string, commitHash string, params *GetEntriesInCommitParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetEntriesInCommitRequest(c.Server, user, repository, commitHash, params) +func (c *Client) Logout(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewLogoutRequest(c.Server) if err != nil { return nil, err } @@ -547,8 +604,8 @@ func (c *Client) GetEntriesInCommit(ctx context.Context, user string, repository return c.Client.Do(req) } -func (c *Client) DeleteObject(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteObjectRequest(c.Server, user, repository, params) +func (c *Client) RegisterWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRegisterRequestWithBody(c.Server, contentType, body) if err != nil { return nil, err } @@ -559,8 +616,8 @@ func (c *Client) DeleteObject(ctx context.Context, user string, repository strin return c.Client.Do(req) } -func (c *Client) GetObject(ctx context.Context, user string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetObjectRequest(c.Server, user, repository, params) +func (c *Client) Register(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRegisterRequest(c.Server, body) if err != nil { return nil, err } @@ -571,8 +628,8 @@ func (c *Client) GetObject(ctx context.Context, user string, repository string, return c.Client.Do(req) } -func (c *Client) HeadObject(ctx context.Context, user string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewHeadObjectRequest(c.Server, user, repository, params) +func (c *Client) ListRepository(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListRepositoryRequest(c.Server) if err != nil { return nil, err } @@ -583,8 +640,8 @@ func (c *Client) HeadObject(ctx context.Context, user string, repository string, return c.Client.Do(req) } -func (c *Client) UploadObjectWithBody(ctx context.Context, user string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewUploadObjectRequestWithBody(c.Server, user, repository, params, contentType, body) +func (c *Client) CreateRepositoryWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateRepositoryRequestWithBody(c.Server, contentType, body) if err != nil { return nil, err } @@ -595,8 +652,8 @@ func (c *Client) UploadObjectWithBody(ctx context.Context, user string, reposito return c.Client.Do(req) } -func (c *Client) CommitWip(ctx context.Context, user string, repository string, refID openapi_types.UUID, params *CommitWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCommitWipRequest(c.Server, user, repository, refID, params) +func (c *Client) CreateRepository(ctx context.Context, body CreateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateRepositoryRequest(c.Server, body) if err != nil { return nil, err } @@ -607,8 +664,8 @@ func (c *Client) CommitWip(ctx context.Context, user string, repository string, return c.Client.Do(req) } -func (c *Client) ListWip(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewListWipRequest(c.Server, user, repository) +func (c *Client) GetUserInfo(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetUserInfoRequest(c.Server) if err != nil { return nil, err } @@ -619,8 +676,8 @@ func (c *Client) ListWip(ctx context.Context, user string, repository string, re return c.Client.Do(req) } -func (c *Client) DeleteWip(ctx context.Context, user string, repository string, refID openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteWipRequest(c.Server, user, repository, refID) +func (c *Client) GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetVersionRequest(c.Server) if err != nil { return nil, err } @@ -631,8 +688,8 @@ func (c *Client) DeleteWip(ctx context.Context, user string, repository string, return c.Client.Do(req) } -func (c *Client) GetWip(ctx context.Context, user string, repository string, refID openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetWipRequest(c.Server, user, repository, refID) +func (c *Client) DeleteWip(ctx context.Context, repository string, params *DeleteWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteWipRequest(c.Server, repository, params) if err != nil { return nil, err } @@ -643,8 +700,8 @@ func (c *Client) GetWip(ctx context.Context, user string, repository string, ref return c.Client.Do(req) } -func (c *Client) CreateWip(ctx context.Context, user string, repository string, refID openapi_types.UUID, params *CreateWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateWipRequest(c.Server, user, repository, refID, params) +func (c *Client) GetWip(ctx context.Context, repository string, params *GetWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetWipRequest(c.Server, repository, params) if err != nil { return nil, err } @@ -655,96 +712,66 @@ func (c *Client) CreateWip(ctx context.Context, user string, repository string, return c.Client.Do(req) } -// NewLoginRequest calls the generic Login builder with application/json body -func NewLoginRequest(server string, body LoginJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) +func (c *Client) CreateWip(ctx context.Context, repository string, params *CreateWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateWipRequest(c.Server, repository, params) if err != nil { return nil, err } - bodyReader = bytes.NewReader(buf) - return NewLoginRequestWithBody(server, "application/json", bodyReader) -} - -// NewLoginRequestWithBody generates requests for Login with any type of body -func NewLoginRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { return nil, err } + return c.Client.Do(req) +} - operationPath := fmt.Sprintf("/auth/login") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) +func (c *Client) CommitWip(ctx context.Context, repository string, params *CommitWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCommitWipRequest(c.Server, repository, params) if err != nil { return nil, err } - - req, err := http.NewRequest("POST", queryURL.String(), body) - if err != nil { + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { return nil, err } - - req.Header.Add("Content-Type", contentType) - - return req, nil + return c.Client.Do(req) } -// NewRegisterRequest calls the generic Register builder with application/json body -func NewRegisterRequest(server string, body RegisterJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) +func (c *Client) ListWip(ctx context.Context, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListWipRequest(c.Server, repository) if err != nil { return nil, err } - bodyReader = bytes.NewReader(buf) - return NewRegisterRequestWithBody(server, "application/json", bodyReader) + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) } -// NewRegisterRequestWithBody generates requests for Register with any type of body -func NewRegisterRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { +// NewDeleteObjectRequest generates requests for DeleteObject +func NewDeleteObjectRequest(server string, user string, repository string, params *DeleteObjectParams) (*http.Request, error) { var err error - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/auth/register") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } + var pathParam0 string - queryURL, err := serverURL.Parse(operationPath) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) if err != nil { return nil, err } - req, err := http.NewRequest("POST", queryURL.String(), body) + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) if err != nil { return nil, err } - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewGetUserInfoRequest generates requests for GetUserInfo -func NewGetUserInfoRequest(server string) (*http.Request, error) { - var err error - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/auth/user") + operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -754,7 +781,49 @@ func NewGetUserInfoRequest(server string) (*http.Request, error) { return nil, err } - req, err := http.NewRequest("GET", queryURL.String(), nil) + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "wipID", runtime.ParamLocationQuery, params.WipID); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "branch", runtime.ParamLocationQuery, params.Branch); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) if err != nil { return nil, err } @@ -762,16 +831,30 @@ func NewGetUserInfoRequest(server string) (*http.Request, error) { return req, nil } -// NewGetVersionRequest generates requests for GetVersion -func NewGetVersionRequest(server string) (*http.Request, error) { +// NewGetObjectRequest generates requests for GetObject +func NewGetObjectRequest(server string, user string, repository string, params *GetObjectParams) (*http.Request, error) { var err error + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/version") + operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -781,16 +864,61 @@ func NewGetVersionRequest(server string) (*http.Request, error) { return nil, err } + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "branch", runtime.ParamLocationQuery, params.Branch); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } + if params != nil { + + if params.Range != nil { + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithLocation("simple", false, "Range", runtime.ParamLocationHeader, *params.Range) + if err != nil { + return nil, err + } + + req.Header.Set("Range", headerParam0) + } + + } + return req, nil } -// NewListRepositoryRequest generates requests for ListRepository -func NewListRepositoryRequest(server string, user string) (*http.Request, error) { +// NewHeadObjectRequest generates requests for HeadObject +func NewHeadObjectRequest(server string, user string, repository string, params *HeadObjectParams) (*http.Request, error) { var err error var pathParam0 string @@ -800,12 +928,19 @@ func NewListRepositoryRequest(server string, user string) (*http.Request, error) return nil, err } + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/%s/repository/list", pathParam0) + operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -815,16 +950,61 @@ func NewListRepositoryRequest(server string, user string) (*http.Request, error) return nil, err } - req, err := http.NewRequest("GET", queryURL.String(), nil) + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "branch", runtime.ParamLocationQuery, params.Branch); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("HEAD", queryURL.String(), nil) if err != nil { return nil, err } + if params != nil { + + if params.Range != nil { + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithLocation("simple", false, "Range", runtime.ParamLocationHeader, *params.Range) + if err != nil { + return nil, err + } + + req.Header.Set("Range", headerParam0) + } + + } + return req, nil } -// NewCreateRepositoryRequest generates requests for CreateRepository -func NewCreateRepositoryRequest(server string, user string, params *CreateRepositoryParams) (*http.Request, error) { +// NewUploadObjectRequestWithBody generates requests for UploadObject with any type of body +func NewUploadObjectRequestWithBody(server string, user string, repository string, params *UploadObjectParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -834,12 +1014,19 @@ func NewCreateRepositoryRequest(server string, user string, params *CreateReposi return nil, err } + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/%s/repository/new", pathParam0) + operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -852,7 +1039,7 @@ func NewCreateRepositoryRequest(server string, user string, params *CreateReposi if params != nil { queryValues := queryURL.Query() - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "name", runtime.ParamLocationQuery, params.Name); err != nil { + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "wipID", runtime.ParamLocationQuery, params.WipID); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -864,30 +1051,55 @@ func NewCreateRepositoryRequest(server string, user string, params *CreateReposi } } - if params.Description != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "description", runtime.ParamLocationQuery, *params.Description); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "branch", runtime.ParamLocationQuery, params.Branch); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) } } + } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } } queryURL.RawQuery = queryValues.Encode() } - req, err := http.NewRequest("POST", queryURL.String(), nil) + req, err := http.NewRequest("POST", queryURL.String(), body) if err != nil { return nil, err } + req.Header.Add("Content-Type", contentType) + + if params != nil { + + if params.IfNoneMatch != nil { + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithLocation("simple", false, "If-None-Match", runtime.ParamLocationHeader, *params.IfNoneMatch) + if err != nil { + return nil, err + } + + req.Header.Set("If-None-Match", headerParam0) + } + + } + return req, nil } @@ -914,7 +1126,7 @@ func NewDeleteRepositoryRequest(server string, user string, repository string) ( return nil, err } - operationPath := fmt.Sprintf("/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/repos/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -955,7 +1167,7 @@ func NewGetRepositoryRequest(server string, user string, repository string) (*ht return nil, err } - operationPath := fmt.Sprintf("/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/repos/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1007,7 +1219,7 @@ func NewUpdateRepositoryRequestWithBody(server string, user string, repository s return nil, err } - operationPath := fmt.Sprintf("/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/repos/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1027,8 +1239,8 @@ func NewUpdateRepositoryRequestWithBody(server string, user string, repository s return req, nil } -// NewGetCommitDiffRequest generates requests for GetCommitDiff -func NewGetCommitDiffRequest(server string, user string, repository string, baseCommit string, toCommit string, params *GetCommitDiffParams) (*http.Request, error) { +// NewGetCommitsInRepositoryRequest generates requests for GetCommitsInRepository +func NewGetCommitsInRepositoryRequest(server string, user string, repository string, params *GetCommitsInRepositoryParams) (*http.Request, error) { var err error var pathParam0 string @@ -1045,26 +1257,12 @@ func NewGetCommitDiffRequest(server string, user string, repository string, base return nil, err } - var pathParam2 string - - pathParam2, err = runtime.StyleParamWithLocation("simple", false, "baseCommit", runtime.ParamLocationPath, baseCommit) - if err != nil { - return nil, err - } - - var pathParam3 string - - pathParam3, err = runtime.StyleParamWithLocation("simple", false, "toCommit", runtime.ParamLocationPath, toCommit) - if err != nil { - return nil, err - } - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/%s/%s/commit/diff/%s/%s", pathParam0, pathParam1, pathParam2, pathParam3) + operationPath := fmt.Sprintf("/repos/%s/%s/commits", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1077,9 +1275,9 @@ func NewGetCommitDiffRequest(server string, user string, repository string, base if params != nil { queryValues := queryURL.Query() - if params.Path != nil { + if params.RefName != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, *params.RefName); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -1104,8 +1302,8 @@ func NewGetCommitDiffRequest(server string, user string, repository string, base return req, nil } -// NewGetEntriesInCommitRequest generates requests for GetEntriesInCommit -func NewGetEntriesInCommitRequest(server string, user string, repository string, commitHash string, params *GetEntriesInCommitParams) (*http.Request, error) { +// NewGetCommitDiffRequest generates requests for GetCommitDiff +func NewGetCommitDiffRequest(server string, user string, repository string, basehead string, params *GetCommitDiffParams) (*http.Request, error) { var err error var pathParam0 string @@ -1124,7 +1322,7 @@ func NewGetEntriesInCommitRequest(server string, user string, repository string, var pathParam2 string - pathParam2, err = runtime.StyleParamWithLocation("simple", false, "commitHash", runtime.ParamLocationPath, commitHash) + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "basehead", runtime.ParamLocationPath, basehead) if err != nil { return nil, err } @@ -1134,7 +1332,7 @@ func NewGetEntriesInCommitRequest(server string, user string, repository string, return nil, err } - operationPath := fmt.Sprintf("/%s/%s/commit/ls/%s", pathParam0, pathParam1, pathParam2) + operationPath := fmt.Sprintf("/repos/%s/%s/compare/%s", pathParam0, pathParam1, pathParam2) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1174,8 +1372,8 @@ func NewGetEntriesInCommitRequest(server string, user string, repository string, return req, nil } -// NewDeleteObjectRequest generates requests for DeleteObject -func NewDeleteObjectRequest(server string, user string, repository string, params *DeleteObjectParams) (*http.Request, error) { +// NewGetEntriesInCommitRequest generates requests for GetEntriesInCommit +func NewGetEntriesInCommitRequest(server string, user string, repository string, params *GetEntriesInCommitParams) (*http.Request, error) { var err error var pathParam0 string @@ -1197,7 +1395,7 @@ func NewDeleteObjectRequest(server string, user string, repository string, param return nil, err } - operationPath := fmt.Sprintf("/%s/%s/object", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/repos/%s/%s/contents/", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1210,46 +1408,42 @@ func NewDeleteObjectRequest(server string, user string, repository string, param if params != nil { queryValues := queryURL.Query() - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "wipID", runtime.ParamLocationQuery, params.WipID); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } + if params.Path != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "branch", runtime.ParamLocationQuery, params.Branch); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } + } - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if params.Ref != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "ref", runtime.ParamLocationQuery, *params.Ref); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } + } queryURL.RawQuery = queryValues.Encode() } - req, err := http.NewRequest("DELETE", queryURL.String(), nil) + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } @@ -1257,30 +1451,27 @@ func NewDeleteObjectRequest(server string, user string, repository string, param return req, nil } -// NewGetObjectRequest generates requests for GetObject -func NewGetObjectRequest(server string, user string, repository string, params *GetObjectParams) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) +// NewLoginRequest calls the generic Login builder with application/json body +func NewLoginRequest(server string, body LoginJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) if err != nil { return nil, err } + bodyReader = bytes.NewReader(buf) + return NewLoginRequestWithBody(server, "application/json", bodyReader) +} - var pathParam1 string - - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) - if err != nil { - return nil, err - } +// NewLoginRequestWithBody generates requests for Login with any type of body +func NewLoginRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/%s/%s/object", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/users/login") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1290,83 +1481,26 @@ func NewGetObjectRequest(server string, user string, repository string, params * return nil, err } - if params != nil { - queryValues := queryURL.Query() - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "branch", runtime.ParamLocationQuery, params.Branch); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - queryURL.RawQuery = queryValues.Encode() - } - - req, err := http.NewRequest("GET", queryURL.String(), nil) + req, err := http.NewRequest("POST", queryURL.String(), body) if err != nil { return nil, err } - if params != nil { - - if params.Range != nil { - var headerParam0 string - - headerParam0, err = runtime.StyleParamWithLocation("simple", false, "Range", runtime.ParamLocationHeader, *params.Range) - if err != nil { - return nil, err - } - - req.Header.Set("Range", headerParam0) - } - - } + req.Header.Add("Content-Type", contentType) return req, nil } -// NewHeadObjectRequest generates requests for HeadObject -func NewHeadObjectRequest(server string, user string, repository string, params *HeadObjectParams) (*http.Request, error) { +// NewLogoutRequest generates requests for Logout +func NewLogoutRequest(server string) (*http.Request, error) { var err error - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) - if err != nil { - return nil, err - } - - var pathParam1 string - - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) - if err != nil { - return nil, err - } - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/%s/%s/object", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/users/logout") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1376,83 +1510,35 @@ func NewHeadObjectRequest(server string, user string, repository string, params return nil, err } - if params != nil { - queryValues := queryURL.Query() - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "branch", runtime.ParamLocationQuery, params.Branch); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - queryURL.RawQuery = queryValues.Encode() - } - - req, err := http.NewRequest("HEAD", queryURL.String(), nil) + req, err := http.NewRequest("POST", queryURL.String(), nil) if err != nil { return nil, err } - if params != nil { - - if params.Range != nil { - var headerParam0 string - - headerParam0, err = runtime.StyleParamWithLocation("simple", false, "Range", runtime.ParamLocationHeader, *params.Range) - if err != nil { - return nil, err - } - - req.Header.Set("Range", headerParam0) - } - - } - return req, nil } -// NewUploadObjectRequestWithBody generates requests for UploadObject with any type of body -func NewUploadObjectRequestWithBody(server string, user string, repository string, params *UploadObjectParams, contentType string, body io.Reader) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) +// NewRegisterRequest calls the generic Register builder with application/json body +func NewRegisterRequest(server string, body RegisterJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) if err != nil { return nil, err } + bodyReader = bytes.NewReader(buf) + return NewRegisterRequestWithBody(server, "application/json", bodyReader) +} - var pathParam1 string - - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) - if err != nil { - return nil, err - } +// NewRegisterRequestWithBody generates requests for Register with any type of body +func NewRegisterRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/%s/%s/object", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/users/register") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1462,48 +1548,6 @@ func NewUploadObjectRequestWithBody(server string, user string, repository strin return nil, err } - if params != nil { - queryValues := queryURL.Query() - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "wipID", runtime.ParamLocationQuery, params.WipID); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "branch", runtime.ParamLocationQuery, params.Branch); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - queryURL.RawQuery = queryValues.Encode() - } - req, err := http.NewRequest("POST", queryURL.String(), body) if err != nil { return nil, err @@ -1511,55 +1555,57 @@ func NewUploadObjectRequestWithBody(server string, user string, repository strin req.Header.Add("Content-Type", contentType) - if params != nil { - - if params.IfNoneMatch != nil { - var headerParam0 string - - headerParam0, err = runtime.StyleParamWithLocation("simple", false, "If-None-Match", runtime.ParamLocationHeader, *params.IfNoneMatch) - if err != nil { - return nil, err - } - - req.Header.Set("If-None-Match", headerParam0) - } - - } - return req, nil } -// NewCommitWipRequest generates requests for CommitWip -func NewCommitWipRequest(server string, user string, repository string, refID openapi_types.UUID, params *CommitWipParams) (*http.Request, error) { +// NewListRepositoryRequest generates requests for ListRepository +func NewListRepositoryRequest(server string) (*http.Request, error) { var err error - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + serverURL, err := url.Parse(server) if err != nil { return nil, err } - var pathParam1 string + operationPath := fmt.Sprintf("/users/repos") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + queryURL, err := serverURL.Parse(operationPath) if err != nil { return nil, err } - var pathParam2 string + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } - pathParam2, err = runtime.StyleParamWithLocation("simple", false, "refID", runtime.ParamLocationPath, refID) + return req, nil +} + +// NewCreateRepositoryRequest calls the generic CreateRepository builder with application/json body +func NewCreateRepositoryRequest(server string, body CreateRepositoryJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) if err != nil { return nil, err } + bodyReader = bytes.NewReader(buf) + return NewCreateRepositoryRequestWithBody(server, "application/json", bodyReader) +} + +// NewCreateRepositoryRequestWithBody generates requests for CreateRepository with any type of body +func NewCreateRepositoryRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/%s/%s/wip/commit/%s", pathParam0, pathParam1, pathParam2) + operationPath := fmt.Sprintf("/users/repos") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1569,60 +1615,53 @@ func NewCommitWipRequest(server string, user string, repository string, refID op return nil, err } - if params != nil { - queryValues := queryURL.Query() - - if params.Msg != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "msg", runtime.ParamLocationQuery, *params.Msg); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - queryURL.RawQuery = queryValues.Encode() - } - - req, err := http.NewRequest("POST", queryURL.String(), nil) + req, err := http.NewRequest("POST", queryURL.String(), body) if err != nil { return nil, err } + req.Header.Add("Content-Type", contentType) + return req, nil } -// NewListWipRequest generates requests for ListWip -func NewListWipRequest(server string, user string, repository string) (*http.Request, error) { +// NewGetUserInfoRequest generates requests for GetUserInfo +func NewGetUserInfoRequest(server string) (*http.Request, error) { var err error - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + serverURL, err := url.Parse(server) if err != nil { return nil, err } - var pathParam1 string + operationPath := fmt.Sprintf("/users/user") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } + return req, nil +} + +// NewGetVersionRequest generates requests for GetVersion +func NewGetVersionRequest(server string) (*http.Request, error) { + var err error + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/%s/%s/wip/list", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/version") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1641,26 +1680,12 @@ func NewListWipRequest(server string, user string, repository string) (*http.Req } // NewDeleteWipRequest generates requests for DeleteWip -func NewDeleteWipRequest(server string, user string, repository string, refID openapi_types.UUID) (*http.Request, error) { +func NewDeleteWipRequest(server string, repository string, params *DeleteWipParams) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) - if err != nil { - return nil, err - } - - var pathParam1 string - - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) - if err != nil { - return nil, err - } - - var pathParam2 string - - pathParam2, err = runtime.StyleParamWithLocation("simple", false, "refID", runtime.ParamLocationPath, refID) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) if err != nil { return nil, err } @@ -1670,7 +1695,7 @@ func NewDeleteWipRequest(server string, user string, repository string, refID op return nil, err } - operationPath := fmt.Sprintf("/%s/%s/wip/%s", pathParam0, pathParam1, pathParam2) + operationPath := fmt.Sprintf("/wip/%s", pathParam0) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1680,6 +1705,24 @@ func NewDeleteWipRequest(server string, user string, repository string, refID op return nil, err } + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + req, err := http.NewRequest("DELETE", queryURL.String(), nil) if err != nil { return nil, err @@ -1689,26 +1732,12 @@ func NewDeleteWipRequest(server string, user string, repository string, refID op } // NewGetWipRequest generates requests for GetWip -func NewGetWipRequest(server string, user string, repository string, refID openapi_types.UUID) (*http.Request, error) { +func NewGetWipRequest(server string, repository string, params *GetWipParams) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) - if err != nil { - return nil, err - } - - var pathParam1 string - - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) - if err != nil { - return nil, err - } - - var pathParam2 string - - pathParam2, err = runtime.StyleParamWithLocation("simple", false, "refID", runtime.ParamLocationPath, refID) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) if err != nil { return nil, err } @@ -1718,7 +1747,7 @@ func NewGetWipRequest(server string, user string, repository string, refID opena return nil, err } - operationPath := fmt.Sprintf("/%s/%s/wip/%s", pathParam0, pathParam1, pathParam2) + operationPath := fmt.Sprintf("/wip/%s", pathParam0) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1728,6 +1757,24 @@ func NewGetWipRequest(server string, user string, repository string, refID opena return nil, err } + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err @@ -1737,26 +1784,88 @@ func NewGetWipRequest(server string, user string, repository string, refID opena } // NewCreateWipRequest generates requests for CreateWip -func NewCreateWipRequest(server string, user string, repository string, refID openapi_types.UUID, params *CreateWipParams) (*http.Request, error) { +func NewCreateWipRequest(server string, repository string, params *CreateWipParams) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) if err != nil { return nil, err } - var pathParam1 string + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + operationPath := fmt.Sprintf("/wip/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) if err != nil { return nil, err } - var pathParam2 string + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "name", runtime.ParamLocationQuery, params.Name); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } - pathParam2, err = runtime.StyleParamWithLocation("simple", false, "refID", runtime.ParamLocationPath, refID) + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "baseCommitID", runtime.ParamLocationQuery, params.BaseCommitID); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("POST", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewCommitWipRequest generates requests for CommitWip +func NewCommitWipRequest(server string, repository string, params *CommitWipParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) if err != nil { return nil, err } @@ -1766,7 +1875,7 @@ func NewCreateWipRequest(server string, user string, repository string, refID op return nil, err } - operationPath := fmt.Sprintf("/%s/%s/wip/%s", pathParam0, pathParam1, pathParam2) + operationPath := fmt.Sprintf("/wip/%s/commit", pathParam0) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1779,7 +1888,23 @@ func NewCreateWipRequest(server string, user string, repository string, refID op if params != nil { queryValues := queryURL.Query() - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "name", runtime.ParamLocationQuery, params.Name); err != nil { + if params.Msg != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "msg", runtime.ParamLocationQuery, *params.Msg); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -1791,22 +1916,44 @@ func NewCreateWipRequest(server string, user string, repository string, refID op } } - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "baseCommitID", runtime.ParamLocationQuery, params.BaseCommitID); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("POST", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewListWipRequest generates requests for ListWip +func NewListWipRequest(server string, repository string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } - queryURL.RawQuery = queryValues.Encode() + serverURL, err := url.Parse(server) + if err != nil { + return nil, err } - req, err := http.NewRequest("POST", queryURL.String(), nil) + operationPath := fmt.Sprintf("/wip/%s/list", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } @@ -1857,27 +2004,17 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { - // LoginWithBodyWithResponse request with any body - LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) - - LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) - - // RegisterWithBodyWithResponse request with any body - RegisterWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterResponse, error) - - RegisterWithResponse(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterResponse, error) - - // GetUserInfoWithResponse request - GetUserInfoWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetUserInfoResponse, error) + // DeleteObjectWithResponse request + DeleteObjectWithResponse(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) - // GetVersionWithResponse request - GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) + // GetObjectWithResponse request + GetObjectWithResponse(ctx context.Context, user string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*GetObjectResponse, error) - // ListRepositoryWithResponse request - ListRepositoryWithResponse(ctx context.Context, user string, reqEditors ...RequestEditorFn) (*ListRepositoryResponse, error) + // HeadObjectWithResponse request + HeadObjectWithResponse(ctx context.Context, user string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*HeadObjectResponse, error) - // CreateRepositoryWithResponse request - CreateRepositoryWithResponse(ctx context.Context, user string, params *CreateRepositoryParams, reqEditors ...RequestEditorFn) (*CreateRepositoryResponse, error) + // UploadObjectWithBodyWithResponse request with any body + UploadObjectWithBodyWithResponse(ctx context.Context, user string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) // DeleteRepositoryWithResponse request DeleteRepositoryWithResponse(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*DeleteRepositoryResponse, error) @@ -1890,48 +2027,65 @@ type ClientWithResponsesInterface interface { UpdateRepositoryWithResponse(ctx context.Context, user string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) + // GetCommitsInRepositoryWithResponse request + GetCommitsInRepositoryWithResponse(ctx context.Context, user string, repository string, params *GetCommitsInRepositoryParams, reqEditors ...RequestEditorFn) (*GetCommitsInRepositoryResponse, error) + // GetCommitDiffWithResponse request - GetCommitDiffWithResponse(ctx context.Context, user string, repository string, baseCommit string, toCommit string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*GetCommitDiffResponse, error) + GetCommitDiffWithResponse(ctx context.Context, user string, repository string, basehead string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*GetCommitDiffResponse, error) // GetEntriesInCommitWithResponse request - GetEntriesInCommitWithResponse(ctx context.Context, user string, repository string, commitHash string, params *GetEntriesInCommitParams, reqEditors ...RequestEditorFn) (*GetEntriesInCommitResponse, error) + GetEntriesInCommitWithResponse(ctx context.Context, user string, repository string, params *GetEntriesInCommitParams, reqEditors ...RequestEditorFn) (*GetEntriesInCommitResponse, error) - // DeleteObjectWithResponse request - DeleteObjectWithResponse(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) + // LoginWithBodyWithResponse request with any body + LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) - // GetObjectWithResponse request - GetObjectWithResponse(ctx context.Context, user string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*GetObjectResponse, error) + LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) - // HeadObjectWithResponse request - HeadObjectWithResponse(ctx context.Context, user string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*HeadObjectResponse, error) + // LogoutWithResponse request + LogoutWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*LogoutResponse, error) - // UploadObjectWithBodyWithResponse request with any body - UploadObjectWithBodyWithResponse(ctx context.Context, user string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) + // RegisterWithBodyWithResponse request with any body + RegisterWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterResponse, error) - // CommitWipWithResponse request - CommitWipWithResponse(ctx context.Context, user string, repository string, refID openapi_types.UUID, params *CommitWipParams, reqEditors ...RequestEditorFn) (*CommitWipResponse, error) + RegisterWithResponse(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterResponse, error) - // ListWipWithResponse request - ListWipWithResponse(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*ListWipResponse, error) + // ListRepositoryWithResponse request + ListRepositoryWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListRepositoryResponse, error) + + // CreateRepositoryWithBodyWithResponse request with any body + CreateRepositoryWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateRepositoryResponse, error) + + CreateRepositoryWithResponse(ctx context.Context, body CreateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateRepositoryResponse, error) + + // GetUserInfoWithResponse request + GetUserInfoWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetUserInfoResponse, error) + + // GetVersionWithResponse request + GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) // DeleteWipWithResponse request - DeleteWipWithResponse(ctx context.Context, user string, repository string, refID openapi_types.UUID, reqEditors ...RequestEditorFn) (*DeleteWipResponse, error) + DeleteWipWithResponse(ctx context.Context, repository string, params *DeleteWipParams, reqEditors ...RequestEditorFn) (*DeleteWipResponse, error) // GetWipWithResponse request - GetWipWithResponse(ctx context.Context, user string, repository string, refID openapi_types.UUID, reqEditors ...RequestEditorFn) (*GetWipResponse, error) + GetWipWithResponse(ctx context.Context, repository string, params *GetWipParams, reqEditors ...RequestEditorFn) (*GetWipResponse, error) // CreateWipWithResponse request - CreateWipWithResponse(ctx context.Context, user string, repository string, refID openapi_types.UUID, params *CreateWipParams, reqEditors ...RequestEditorFn) (*CreateWipResponse, error) + CreateWipWithResponse(ctx context.Context, repository string, params *CreateWipParams, reqEditors ...RequestEditorFn) (*CreateWipResponse, error) + + // CommitWipWithResponse request + CommitWipWithResponse(ctx context.Context, repository string, params *CommitWipParams, reqEditors ...RequestEditorFn) (*CommitWipResponse, error) + + // ListWipWithResponse request + ListWipWithResponse(ctx context.Context, repository string, reqEditors ...RequestEditorFn) (*ListWipResponse, error) } -type LoginResponse struct { +type DeleteObjectResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *AuthenticationToken } // Status returns HTTPResponse.Status -func (r LoginResponse) Status() string { +func (r DeleteObjectResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -1939,20 +2093,20 @@ func (r LoginResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r LoginResponse) StatusCode() int { +func (r DeleteObjectResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type RegisterResponse struct { +type GetObjectResponse struct { Body []byte HTTPResponse *http.Response } // Status returns HTTPResponse.Status -func (r RegisterResponse) Status() string { +func (r GetObjectResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -1960,21 +2114,20 @@ func (r RegisterResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r RegisterResponse) StatusCode() int { +func (r GetObjectResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetUserInfoResponse struct { +type HeadObjectResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *UserInfo } // Status returns HTTPResponse.Status -func (r GetUserInfoResponse) Status() string { +func (r HeadObjectResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -1982,21 +2135,21 @@ func (r GetUserInfoResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetUserInfoResponse) StatusCode() int { +func (r HeadObjectResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetVersionResponse struct { +type UploadObjectResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *VersionResult + JSON201 *ObjectStats } // Status returns HTTPResponse.Status -func (r GetVersionResponse) Status() string { +func (r UploadObjectResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2004,21 +2157,20 @@ func (r GetVersionResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetVersionResponse) StatusCode() int { +func (r UploadObjectResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type ListRepositoryResponse struct { +type DeleteRepositoryResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *[]Repository } // Status returns HTTPResponse.Status -func (r ListRepositoryResponse) Status() string { +func (r DeleteRepositoryResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2026,21 +2178,21 @@ func (r ListRepositoryResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r ListRepositoryResponse) StatusCode() int { +func (r DeleteRepositoryResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type CreateRepositoryResponse struct { +type GetRepositoryResponse struct { Body []byte HTTPResponse *http.Response - JSON201 *[]Repository + JSON200 *Repository } // Status returns HTTPResponse.Status -func (r CreateRepositoryResponse) Status() string { +func (r GetRepositoryResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2048,20 +2200,20 @@ func (r CreateRepositoryResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r CreateRepositoryResponse) StatusCode() int { +func (r GetRepositoryResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type DeleteRepositoryResponse struct { +type UpdateRepositoryResponse struct { Body []byte HTTPResponse *http.Response } // Status returns HTTPResponse.Status -func (r DeleteRepositoryResponse) Status() string { +func (r UpdateRepositoryResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2069,21 +2221,21 @@ func (r DeleteRepositoryResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r DeleteRepositoryResponse) StatusCode() int { +func (r UpdateRepositoryResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetRepositoryResponse struct { +type GetCommitsInRepositoryResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *Repository + JSON200 *[]Commit } // Status returns HTTPResponse.Status -func (r GetRepositoryResponse) Status() string { +func (r GetCommitsInRepositoryResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2091,20 +2243,21 @@ func (r GetRepositoryResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetRepositoryResponse) StatusCode() int { +func (r GetCommitsInRepositoryResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type UpdateRepositoryResponse struct { +type GetCommitDiffResponse struct { Body []byte HTTPResponse *http.Response + JSON200 *[]Change } // Status returns HTTPResponse.Status -func (r UpdateRepositoryResponse) Status() string { +func (r GetCommitDiffResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2112,21 +2265,21 @@ func (r UpdateRepositoryResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r UpdateRepositoryResponse) StatusCode() int { +func (r GetCommitDiffResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetCommitDiffResponse struct { +type GetEntriesInCommitResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *[]Change + JSON200 *[]TreeEntry } // Status returns HTTPResponse.Status -func (r GetCommitDiffResponse) Status() string { +func (r GetEntriesInCommitResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2134,21 +2287,21 @@ func (r GetCommitDiffResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetCommitDiffResponse) StatusCode() int { +func (r GetEntriesInCommitResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetEntriesInCommitResponse struct { +type LoginResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *[]TreeEntry + JSON200 *AuthenticationToken } // Status returns HTTPResponse.Status -func (r GetEntriesInCommitResponse) Status() string { +func (r LoginResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2156,20 +2309,20 @@ func (r GetEntriesInCommitResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetEntriesInCommitResponse) StatusCode() int { +func (r LoginResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type DeleteObjectResponse struct { +type LogoutResponse struct { Body []byte HTTPResponse *http.Response } // Status returns HTTPResponse.Status -func (r DeleteObjectResponse) Status() string { +func (r LogoutResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2177,20 +2330,20 @@ func (r DeleteObjectResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r DeleteObjectResponse) StatusCode() int { +func (r LogoutResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetObjectResponse struct { +type RegisterResponse struct { Body []byte HTTPResponse *http.Response } // Status returns HTTPResponse.Status -func (r GetObjectResponse) Status() string { +func (r RegisterResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2198,20 +2351,21 @@ func (r GetObjectResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetObjectResponse) StatusCode() int { +func (r RegisterResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type HeadObjectResponse struct { +type ListRepositoryResponse struct { Body []byte HTTPResponse *http.Response + JSON200 *[]Repository } // Status returns HTTPResponse.Status -func (r HeadObjectResponse) Status() string { +func (r ListRepositoryResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2219,21 +2373,21 @@ func (r HeadObjectResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r HeadObjectResponse) StatusCode() int { +func (r ListRepositoryResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type UploadObjectResponse struct { +type CreateRepositoryResponse struct { Body []byte HTTPResponse *http.Response - JSON201 *ObjectStats + JSON201 *[]Repository } // Status returns HTTPResponse.Status -func (r UploadObjectResponse) Status() string { +func (r CreateRepositoryResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2241,21 +2395,21 @@ func (r UploadObjectResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r UploadObjectResponse) StatusCode() int { +func (r CreateRepositoryResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type CommitWipResponse struct { +type GetUserInfoResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *Wip + JSON200 *UserInfo } // Status returns HTTPResponse.Status -func (r CommitWipResponse) Status() string { +func (r GetUserInfoResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2263,21 +2417,21 @@ func (r CommitWipResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r CommitWipResponse) StatusCode() int { +func (r GetUserInfoResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type ListWipResponse struct { +type GetVersionResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *[]Wip + JSON200 *VersionResult } // Status returns HTTPResponse.Status -func (r ListWipResponse) Status() string { +func (r GetVersionResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2285,7 +2439,7 @@ func (r ListWipResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r ListWipResponse) StatusCode() int { +func (r GetVersionResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -2357,74 +2511,84 @@ func (r CreateWipResponse) StatusCode() int { return 0 } -// LoginWithBodyWithResponse request with arbitrary body returning *LoginResponse -func (c *ClientWithResponses) LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) { - rsp, err := c.LoginWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err +type CommitWipResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Wip +} + +// Status returns HTTPResponse.Status +func (r CommitWipResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } - return ParseLoginResponse(rsp) + return http.StatusText(0) } -func (c *ClientWithResponses) LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) { - rsp, err := c.Login(ctx, body, reqEditors...) - if err != nil { - return nil, err +// StatusCode returns HTTPResponse.StatusCode +func (r CommitWipResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } - return ParseLoginResponse(rsp) + return 0 } -// RegisterWithBodyWithResponse request with arbitrary body returning *RegisterResponse -func (c *ClientWithResponses) RegisterWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterResponse, error) { - rsp, err := c.RegisterWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err +type ListWipResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]Wip +} + +// Status returns HTTPResponse.Status +func (r ListWipResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } - return ParseRegisterResponse(rsp) + return http.StatusText(0) } -func (c *ClientWithResponses) RegisterWithResponse(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterResponse, error) { - rsp, err := c.Register(ctx, body, reqEditors...) - if err != nil { - return nil, err +// StatusCode returns HTTPResponse.StatusCode +func (r ListWipResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } - return ParseRegisterResponse(rsp) + return 0 } -// GetUserInfoWithResponse request returning *GetUserInfoResponse -func (c *ClientWithResponses) GetUserInfoWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetUserInfoResponse, error) { - rsp, err := c.GetUserInfo(ctx, reqEditors...) +// DeleteObjectWithResponse request returning *DeleteObjectResponse +func (c *ClientWithResponses) DeleteObjectWithResponse(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) { + rsp, err := c.DeleteObject(ctx, user, repository, params, reqEditors...) if err != nil { return nil, err } - return ParseGetUserInfoResponse(rsp) + return ParseDeleteObjectResponse(rsp) } -// GetVersionWithResponse request returning *GetVersionResponse -func (c *ClientWithResponses) GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) { - rsp, err := c.GetVersion(ctx, reqEditors...) +// GetObjectWithResponse request returning *GetObjectResponse +func (c *ClientWithResponses) GetObjectWithResponse(ctx context.Context, user string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*GetObjectResponse, error) { + rsp, err := c.GetObject(ctx, user, repository, params, reqEditors...) if err != nil { return nil, err } - return ParseGetVersionResponse(rsp) + return ParseGetObjectResponse(rsp) } -// ListRepositoryWithResponse request returning *ListRepositoryResponse -func (c *ClientWithResponses) ListRepositoryWithResponse(ctx context.Context, user string, reqEditors ...RequestEditorFn) (*ListRepositoryResponse, error) { - rsp, err := c.ListRepository(ctx, user, reqEditors...) +// HeadObjectWithResponse request returning *HeadObjectResponse +func (c *ClientWithResponses) HeadObjectWithResponse(ctx context.Context, user string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*HeadObjectResponse, error) { + rsp, err := c.HeadObject(ctx, user, repository, params, reqEditors...) if err != nil { return nil, err } - return ParseListRepositoryResponse(rsp) + return ParseHeadObjectResponse(rsp) } -// CreateRepositoryWithResponse request returning *CreateRepositoryResponse -func (c *ClientWithResponses) CreateRepositoryWithResponse(ctx context.Context, user string, params *CreateRepositoryParams, reqEditors ...RequestEditorFn) (*CreateRepositoryResponse, error) { - rsp, err := c.CreateRepository(ctx, user, params, reqEditors...) +// UploadObjectWithBodyWithResponse request with arbitrary body returning *UploadObjectResponse +func (c *ClientWithResponses) UploadObjectWithBodyWithResponse(ctx context.Context, user string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) { + rsp, err := c.UploadObjectWithBody(ctx, user, repository, params, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseCreateRepositoryResponse(rsp) + return ParseUploadObjectResponse(rsp) } // DeleteRepositoryWithResponse request returning *DeleteRepositoryResponse @@ -2462,9 +2626,18 @@ func (c *ClientWithResponses) UpdateRepositoryWithResponse(ctx context.Context, return ParseUpdateRepositoryResponse(rsp) } +// GetCommitsInRepositoryWithResponse request returning *GetCommitsInRepositoryResponse +func (c *ClientWithResponses) GetCommitsInRepositoryWithResponse(ctx context.Context, user string, repository string, params *GetCommitsInRepositoryParams, reqEditors ...RequestEditorFn) (*GetCommitsInRepositoryResponse, error) { + rsp, err := c.GetCommitsInRepository(ctx, user, repository, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetCommitsInRepositoryResponse(rsp) +} + // GetCommitDiffWithResponse request returning *GetCommitDiffResponse -func (c *ClientWithResponses) GetCommitDiffWithResponse(ctx context.Context, user string, repository string, baseCommit string, toCommit string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*GetCommitDiffResponse, error) { - rsp, err := c.GetCommitDiff(ctx, user, repository, baseCommit, toCommit, params, reqEditors...) +func (c *ClientWithResponses) GetCommitDiffWithResponse(ctx context.Context, user string, repository string, basehead string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*GetCommitDiffResponse, error) { + rsp, err := c.GetCommitDiff(ctx, user, repository, basehead, params, reqEditors...) if err != nil { return nil, err } @@ -2472,71 +2645,104 @@ func (c *ClientWithResponses) GetCommitDiffWithResponse(ctx context.Context, use } // GetEntriesInCommitWithResponse request returning *GetEntriesInCommitResponse -func (c *ClientWithResponses) GetEntriesInCommitWithResponse(ctx context.Context, user string, repository string, commitHash string, params *GetEntriesInCommitParams, reqEditors ...RequestEditorFn) (*GetEntriesInCommitResponse, error) { - rsp, err := c.GetEntriesInCommit(ctx, user, repository, commitHash, params, reqEditors...) +func (c *ClientWithResponses) GetEntriesInCommitWithResponse(ctx context.Context, user string, repository string, params *GetEntriesInCommitParams, reqEditors ...RequestEditorFn) (*GetEntriesInCommitResponse, error) { + rsp, err := c.GetEntriesInCommit(ctx, user, repository, params, reqEditors...) if err != nil { return nil, err } return ParseGetEntriesInCommitResponse(rsp) } -// DeleteObjectWithResponse request returning *DeleteObjectResponse -func (c *ClientWithResponses) DeleteObjectWithResponse(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) { - rsp, err := c.DeleteObject(ctx, user, repository, params, reqEditors...) +// LoginWithBodyWithResponse request with arbitrary body returning *LoginResponse +func (c *ClientWithResponses) LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) { + rsp, err := c.LoginWithBody(ctx, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseDeleteObjectResponse(rsp) + return ParseLoginResponse(rsp) } -// GetObjectWithResponse request returning *GetObjectResponse -func (c *ClientWithResponses) GetObjectWithResponse(ctx context.Context, user string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*GetObjectResponse, error) { - rsp, err := c.GetObject(ctx, user, repository, params, reqEditors...) +func (c *ClientWithResponses) LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) { + rsp, err := c.Login(ctx, body, reqEditors...) if err != nil { return nil, err } - return ParseGetObjectResponse(rsp) + return ParseLoginResponse(rsp) } -// HeadObjectWithResponse request returning *HeadObjectResponse -func (c *ClientWithResponses) HeadObjectWithResponse(ctx context.Context, user string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*HeadObjectResponse, error) { - rsp, err := c.HeadObject(ctx, user, repository, params, reqEditors...) +// LogoutWithResponse request returning *LogoutResponse +func (c *ClientWithResponses) LogoutWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*LogoutResponse, error) { + rsp, err := c.Logout(ctx, reqEditors...) if err != nil { return nil, err } - return ParseHeadObjectResponse(rsp) + return ParseLogoutResponse(rsp) } -// UploadObjectWithBodyWithResponse request with arbitrary body returning *UploadObjectResponse -func (c *ClientWithResponses) UploadObjectWithBodyWithResponse(ctx context.Context, user string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) { - rsp, err := c.UploadObjectWithBody(ctx, user, repository, params, contentType, body, reqEditors...) +// RegisterWithBodyWithResponse request with arbitrary body returning *RegisterResponse +func (c *ClientWithResponses) RegisterWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterResponse, error) { + rsp, err := c.RegisterWithBody(ctx, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseUploadObjectResponse(rsp) + return ParseRegisterResponse(rsp) } -// CommitWipWithResponse request returning *CommitWipResponse -func (c *ClientWithResponses) CommitWipWithResponse(ctx context.Context, user string, repository string, refID openapi_types.UUID, params *CommitWipParams, reqEditors ...RequestEditorFn) (*CommitWipResponse, error) { - rsp, err := c.CommitWip(ctx, user, repository, refID, params, reqEditors...) +func (c *ClientWithResponses) RegisterWithResponse(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterResponse, error) { + rsp, err := c.Register(ctx, body, reqEditors...) if err != nil { return nil, err } - return ParseCommitWipResponse(rsp) + return ParseRegisterResponse(rsp) } -// ListWipWithResponse request returning *ListWipResponse -func (c *ClientWithResponses) ListWipWithResponse(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*ListWipResponse, error) { - rsp, err := c.ListWip(ctx, user, repository, reqEditors...) +// ListRepositoryWithResponse request returning *ListRepositoryResponse +func (c *ClientWithResponses) ListRepositoryWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListRepositoryResponse, error) { + rsp, err := c.ListRepository(ctx, reqEditors...) if err != nil { return nil, err } - return ParseListWipResponse(rsp) + return ParseListRepositoryResponse(rsp) +} + +// CreateRepositoryWithBodyWithResponse request with arbitrary body returning *CreateRepositoryResponse +func (c *ClientWithResponses) CreateRepositoryWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateRepositoryResponse, error) { + rsp, err := c.CreateRepositoryWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateRepositoryResponse(rsp) +} + +func (c *ClientWithResponses) CreateRepositoryWithResponse(ctx context.Context, body CreateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateRepositoryResponse, error) { + rsp, err := c.CreateRepository(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateRepositoryResponse(rsp) +} + +// GetUserInfoWithResponse request returning *GetUserInfoResponse +func (c *ClientWithResponses) GetUserInfoWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetUserInfoResponse, error) { + rsp, err := c.GetUserInfo(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetUserInfoResponse(rsp) +} + +// GetVersionWithResponse request returning *GetVersionResponse +func (c *ClientWithResponses) GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) { + rsp, err := c.GetVersion(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetVersionResponse(rsp) } // DeleteWipWithResponse request returning *DeleteWipResponse -func (c *ClientWithResponses) DeleteWipWithResponse(ctx context.Context, user string, repository string, refID openapi_types.UUID, reqEditors ...RequestEditorFn) (*DeleteWipResponse, error) { - rsp, err := c.DeleteWip(ctx, user, repository, refID, reqEditors...) +func (c *ClientWithResponses) DeleteWipWithResponse(ctx context.Context, repository string, params *DeleteWipParams, reqEditors ...RequestEditorFn) (*DeleteWipResponse, error) { + rsp, err := c.DeleteWip(ctx, repository, params, reqEditors...) if err != nil { return nil, err } @@ -2544,8 +2750,8 @@ func (c *ClientWithResponses) DeleteWipWithResponse(ctx context.Context, user st } // GetWipWithResponse request returning *GetWipResponse -func (c *ClientWithResponses) GetWipWithResponse(ctx context.Context, user string, repository string, refID openapi_types.UUID, reqEditors ...RequestEditorFn) (*GetWipResponse, error) { - rsp, err := c.GetWip(ctx, user, repository, refID, reqEditors...) +func (c *ClientWithResponses) GetWipWithResponse(ctx context.Context, repository string, params *GetWipParams, reqEditors ...RequestEditorFn) (*GetWipResponse, error) { + rsp, err := c.GetWip(ctx, repository, params, reqEditors...) if err != nil { return nil, err } @@ -2553,49 +2759,57 @@ func (c *ClientWithResponses) GetWipWithResponse(ctx context.Context, user strin } // CreateWipWithResponse request returning *CreateWipResponse -func (c *ClientWithResponses) CreateWipWithResponse(ctx context.Context, user string, repository string, refID openapi_types.UUID, params *CreateWipParams, reqEditors ...RequestEditorFn) (*CreateWipResponse, error) { - rsp, err := c.CreateWip(ctx, user, repository, refID, params, reqEditors...) +func (c *ClientWithResponses) CreateWipWithResponse(ctx context.Context, repository string, params *CreateWipParams, reqEditors ...RequestEditorFn) (*CreateWipResponse, error) { + rsp, err := c.CreateWip(ctx, repository, params, reqEditors...) if err != nil { return nil, err } return ParseCreateWipResponse(rsp) } -// ParseLoginResponse parses an HTTP response from a LoginWithResponse call -func ParseLoginResponse(rsp *http.Response) (*LoginResponse, error) { +// CommitWipWithResponse request returning *CommitWipResponse +func (c *ClientWithResponses) CommitWipWithResponse(ctx context.Context, repository string, params *CommitWipParams, reqEditors ...RequestEditorFn) (*CommitWipResponse, error) { + rsp, err := c.CommitWip(ctx, repository, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseCommitWipResponse(rsp) +} + +// ListWipWithResponse request returning *ListWipResponse +func (c *ClientWithResponses) ListWipWithResponse(ctx context.Context, repository string, reqEditors ...RequestEditorFn) (*ListWipResponse, error) { + rsp, err := c.ListWip(ctx, repository, reqEditors...) + if err != nil { + return nil, err + } + return ParseListWipResponse(rsp) +} + +// ParseDeleteObjectResponse parses an HTTP response from a DeleteObjectWithResponse call +func ParseDeleteObjectResponse(rsp *http.Response) (*DeleteObjectResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &LoginResponse{ + response := &DeleteObjectResponse{ Body: bodyBytes, HTTPResponse: rsp, } - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest AuthenticationToken - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - } - return response, nil } -// ParseRegisterResponse parses an HTTP response from a RegisterWithResponse call -func ParseRegisterResponse(rsp *http.Response) (*RegisterResponse, error) { +// ParseGetObjectResponse parses an HTTP response from a GetObjectWithResponse call +func ParseGetObjectResponse(rsp *http.Response) (*GetObjectResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &RegisterResponse{ + response := &GetObjectResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -2603,119 +2817,99 @@ func ParseRegisterResponse(rsp *http.Response) (*RegisterResponse, error) { return response, nil } -// ParseGetUserInfoResponse parses an HTTP response from a GetUserInfoWithResponse call -func ParseGetUserInfoResponse(rsp *http.Response) (*GetUserInfoResponse, error) { +// ParseHeadObjectResponse parses an HTTP response from a HeadObjectWithResponse call +func ParseHeadObjectResponse(rsp *http.Response) (*HeadObjectResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetUserInfoResponse{ + response := &HeadObjectResponse{ Body: bodyBytes, HTTPResponse: rsp, } - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest UserInfo - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - } - return response, nil } -// ParseGetVersionResponse parses an HTTP response from a GetVersionWithResponse call -func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) { +// ParseUploadObjectResponse parses an HTTP response from a UploadObjectWithResponse call +func ParseUploadObjectResponse(rsp *http.Response) (*UploadObjectResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetVersionResponse{ + response := &UploadObjectResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest VersionResult + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest ObjectStats if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON200 = &dest + response.JSON201 = &dest } return response, nil } -// ParseListRepositoryResponse parses an HTTP response from a ListRepositoryWithResponse call -func ParseListRepositoryResponse(rsp *http.Response) (*ListRepositoryResponse, error) { +// ParseDeleteRepositoryResponse parses an HTTP response from a DeleteRepositoryWithResponse call +func ParseDeleteRepositoryResponse(rsp *http.Response) (*DeleteRepositoryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &ListRepositoryResponse{ + response := &DeleteRepositoryResponse{ Body: bodyBytes, HTTPResponse: rsp, } - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []Repository - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - } - return response, nil } -// ParseCreateRepositoryResponse parses an HTTP response from a CreateRepositoryWithResponse call -func ParseCreateRepositoryResponse(rsp *http.Response) (*CreateRepositoryResponse, error) { +// ParseGetRepositoryResponse parses an HTTP response from a GetRepositoryWithResponse call +func ParseGetRepositoryResponse(rsp *http.Response) (*GetRepositoryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CreateRepositoryResponse{ + response := &GetRepositoryResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest []Repository + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Repository if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON201 = &dest + response.JSON200 = &dest } return response, nil } -// ParseDeleteRepositoryResponse parses an HTTP response from a DeleteRepositoryWithResponse call -func ParseDeleteRepositoryResponse(rsp *http.Response) (*DeleteRepositoryResponse, error) { +// ParseUpdateRepositoryResponse parses an HTTP response from a UpdateRepositoryWithResponse call +func ParseUpdateRepositoryResponse(rsp *http.Response) (*UpdateRepositoryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &DeleteRepositoryResponse{ + response := &UpdateRepositoryResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -2723,22 +2917,22 @@ func ParseDeleteRepositoryResponse(rsp *http.Response) (*DeleteRepositoryRespons return response, nil } -// ParseGetRepositoryResponse parses an HTTP response from a GetRepositoryWithResponse call -func ParseGetRepositoryResponse(rsp *http.Response) (*GetRepositoryResponse, error) { +// ParseGetCommitsInRepositoryResponse parses an HTTP response from a GetCommitsInRepositoryWithResponse call +func ParseGetCommitsInRepositoryResponse(rsp *http.Response) (*GetCommitsInRepositoryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetRepositoryResponse{ + response := &GetCommitsInRepositoryResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest Repository + var dest []Commit if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -2749,38 +2943,48 @@ func ParseGetRepositoryResponse(rsp *http.Response) (*GetRepositoryResponse, err return response, nil } -// ParseUpdateRepositoryResponse parses an HTTP response from a UpdateRepositoryWithResponse call -func ParseUpdateRepositoryResponse(rsp *http.Response) (*UpdateRepositoryResponse, error) { +// ParseGetCommitDiffResponse parses an HTTP response from a GetCommitDiffWithResponse call +func ParseGetCommitDiffResponse(rsp *http.Response) (*GetCommitDiffResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &UpdateRepositoryResponse{ + response := &GetCommitDiffResponse{ Body: bodyBytes, HTTPResponse: rsp, } + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []Change + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + return response, nil } -// ParseGetCommitDiffResponse parses an HTTP response from a GetCommitDiffWithResponse call -func ParseGetCommitDiffResponse(rsp *http.Response) (*GetCommitDiffResponse, error) { +// ParseGetEntriesInCommitResponse parses an HTTP response from a GetEntriesInCommitWithResponse call +func ParseGetEntriesInCommitResponse(rsp *http.Response) (*GetEntriesInCommitResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetCommitDiffResponse{ + response := &GetEntriesInCommitResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []Change + var dest []TreeEntry if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -2791,22 +2995,22 @@ func ParseGetCommitDiffResponse(rsp *http.Response) (*GetCommitDiffResponse, err return response, nil } -// ParseGetEntriesInCommitResponse parses an HTTP response from a GetEntriesInCommitWithResponse call -func ParseGetEntriesInCommitResponse(rsp *http.Response) (*GetEntriesInCommitResponse, error) { +// ParseLoginResponse parses an HTTP response from a LoginWithResponse call +func ParseLoginResponse(rsp *http.Response) (*LoginResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetEntriesInCommitResponse{ + response := &LoginResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []TreeEntry + var dest AuthenticationToken if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -2817,15 +3021,15 @@ func ParseGetEntriesInCommitResponse(rsp *http.Response) (*GetEntriesInCommitRes return response, nil } -// ParseDeleteObjectResponse parses an HTTP response from a DeleteObjectWithResponse call -func ParseDeleteObjectResponse(rsp *http.Response) (*DeleteObjectResponse, error) { +// ParseLogoutResponse parses an HTTP response from a LogoutWithResponse call +func ParseLogoutResponse(rsp *http.Response) (*LogoutResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &DeleteObjectResponse{ + response := &LogoutResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -2833,15 +3037,15 @@ func ParseDeleteObjectResponse(rsp *http.Response) (*DeleteObjectResponse, error return response, nil } -// ParseGetObjectResponse parses an HTTP response from a GetObjectWithResponse call -func ParseGetObjectResponse(rsp *http.Response) (*GetObjectResponse, error) { +// ParseRegisterResponse parses an HTTP response from a RegisterWithResponse call +func ParseRegisterResponse(rsp *http.Response) (*RegisterResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetObjectResponse{ + response := &RegisterResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -2849,38 +3053,48 @@ func ParseGetObjectResponse(rsp *http.Response) (*GetObjectResponse, error) { return response, nil } -// ParseHeadObjectResponse parses an HTTP response from a HeadObjectWithResponse call -func ParseHeadObjectResponse(rsp *http.Response) (*HeadObjectResponse, error) { +// ParseListRepositoryResponse parses an HTTP response from a ListRepositoryWithResponse call +func ParseListRepositoryResponse(rsp *http.Response) (*ListRepositoryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &HeadObjectResponse{ + response := &ListRepositoryResponse{ Body: bodyBytes, HTTPResponse: rsp, } + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []Repository + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + return response, nil } -// ParseUploadObjectResponse parses an HTTP response from a UploadObjectWithResponse call -func ParseUploadObjectResponse(rsp *http.Response) (*UploadObjectResponse, error) { +// ParseCreateRepositoryResponse parses an HTTP response from a CreateRepositoryWithResponse call +func ParseCreateRepositoryResponse(rsp *http.Response) (*CreateRepositoryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &UploadObjectResponse{ + response := &CreateRepositoryResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest ObjectStats + var dest []Repository if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -2891,22 +3105,22 @@ func ParseUploadObjectResponse(rsp *http.Response) (*UploadObjectResponse, error return response, nil } -// ParseCommitWipResponse parses an HTTP response from a CommitWipWithResponse call -func ParseCommitWipResponse(rsp *http.Response) (*CommitWipResponse, error) { +// ParseGetUserInfoResponse parses an HTTP response from a GetUserInfoWithResponse call +func ParseGetUserInfoResponse(rsp *http.Response) (*GetUserInfoResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CommitWipResponse{ + response := &GetUserInfoResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest Wip + var dest UserInfo if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -2917,22 +3131,22 @@ func ParseCommitWipResponse(rsp *http.Response) (*CommitWipResponse, error) { return response, nil } -// ParseListWipResponse parses an HTTP response from a ListWipWithResponse call -func ParseListWipResponse(rsp *http.Response) (*ListWipResponse, error) { +// ParseGetVersionResponse parses an HTTP response from a GetVersionWithResponse call +func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &ListWipResponse{ + response := &GetVersionResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []Wip + var dest VersionResult if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -3011,190 +3225,260 @@ func ParseCreateWipResponse(rsp *http.Response) (*CreateWipResponse, error) { return response, nil } +// ParseCommitWipResponse parses an HTTP response from a CommitWipWithResponse call +func ParseCommitWipResponse(rsp *http.Response) (*CommitWipResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CommitWipResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Wip + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseListWipResponse parses an HTTP response from a ListWipWithResponse call +func ParseListWipResponse(rsp *http.Response) (*ListWipResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListWipResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []Wip + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ServerInterface represents all server handlers. type ServerInterface interface { + // delete object. Missing objects will not return a NotFound error. + // (DELETE /object/{user}/{repository}) + DeleteObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params DeleteObjectParams) + // get object content + // (GET /object/{user}/{repository}) + GetObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params GetObjectParams) + // check if object exists + // (HEAD /object/{user}/{repository}) + HeadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params HeadObjectParams) + + // (POST /object/{user}/{repository}) + UploadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params UploadObjectParams) + // delete repository + // (DELETE /repos/{user}/{repository}) + DeleteRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string) + // get repository + // (GET /repos/{user}/{repository}) + GetRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string) + // update repository + // (POST /repos/{user}/{repository}) + UpdateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateRepositoryJSONRequestBody, user string, repository string) + // get commits in repository + // (GET /repos/{user}/{repository}/commits) + GetCommitsInRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params GetCommitsInRepositoryParams) + // get commit differences + // (GET /repos/{user}/{repository}/compare/{basehead}) + GetCommitDiff(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, basehead string, params GetCommitDiffParams) + // list entries in commit + // (GET /repos/{user}/{repository}/contents/) + GetEntriesInCommit(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params GetEntriesInCommitParams) // perform a login - // (POST /auth/login) + // (POST /users/login) Login(ctx context.Context, w *JiaozifsResponse, r *http.Request, body LoginJSONRequestBody) + // perform a logout + // (POST /users/logout) + Logout(ctx context.Context, w *JiaozifsResponse, r *http.Request) // perform user registration - // (POST /auth/register) + // (POST /users/register) Register(ctx context.Context, w *JiaozifsResponse, r *http.Request, body RegisterJSONRequestBody) + // list repository + // (GET /users/repos) + ListRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request) + // create repository + // (POST /users/repos) + CreateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateRepositoryJSONRequestBody) // get information of the currently logged-in user - // (GET /auth/user) + // (GET /users/user) GetUserInfo(ctx context.Context, w *JiaozifsResponse, r *http.Request) // return program and runtime version // (GET /version) GetVersion(ctx context.Context, w *JiaozifsResponse, r *http.Request) - // list repository - // (GET /{user}/repository/list) - ListRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string) - // create repository - // (POST /{user}/repository/new) - CreateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, params CreateRepositoryParams) - // delete repository - // (DELETE /{user}/{repository}) - DeleteRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string) - // get repository - // (GET /{user}/{repository}) - GetRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string) - // update repository - // (POST /{user}/{repository}) - UpdateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateRepositoryJSONRequestBody, user string, repository string) - // get commit differences - // (GET /{user}/{repository}/commit/diff/{baseCommit}/{toCommit}) - GetCommitDiff(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, baseCommit string, toCommit string, params GetCommitDiffParams) - // list entries in commit - // (GET /{user}/{repository}/commit/ls/{commitHash}) - GetEntriesInCommit(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, commitHash string, params GetEntriesInCommitParams) - // delete object. Missing objects will not return a NotFound error. - // (DELETE /{user}/{repository}/object) - DeleteObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params DeleteObjectParams) - // get object content - // (GET /{user}/{repository}/object) - GetObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params GetObjectParams) - // check if object exists - // (HEAD /{user}/{repository}/object) - HeadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params HeadObjectParams) - - // (POST /{user}/{repository}/object) - UploadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params UploadObjectParams) - // commit working in process to branch - // (POST /{user}/{repository}/wip/commit/{refID}) - CommitWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, refID openapi_types.UUID, params CommitWipParams) - // list wip in specific project and user - // (GET /{user}/{repository}/wip/list) - ListWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string) // remove working in process - // (DELETE /{user}/{repository}/wip/{refID}) - DeleteWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, refID openapi_types.UUID) + // (DELETE /wip/{repository}) + DeleteWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string, params DeleteWipParams) // get working in process - // (GET /{user}/{repository}/wip/{refID}) - GetWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, refID openapi_types.UUID) + // (GET /wip/{repository}) + GetWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string, params GetWipParams) // create working in process - // (POST /{user}/{repository}/wip/{refID}) - CreateWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, refID openapi_types.UUID, params CreateWipParams) + // (POST /wip/{repository}) + CreateWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string, params CreateWipParams) + // commit working in process to branch + // (POST /wip/{repository}/commit) + CommitWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string, params CommitWipParams) + // list wip in specific project and user + // (GET /wip/{repository}/list) + ListWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string) } // Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. type Unimplemented struct{} -// perform a login -// (POST /auth/login) -func (_ Unimplemented) Login(ctx context.Context, w *JiaozifsResponse, r *http.Request, body LoginJSONRequestBody) { - w.WriteHeader(http.StatusNotImplemented) -} - -// perform user registration -// (POST /auth/register) -func (_ Unimplemented) Register(ctx context.Context, w *JiaozifsResponse, r *http.Request, body RegisterJSONRequestBody) { - w.WriteHeader(http.StatusNotImplemented) -} - -// get information of the currently logged-in user -// (GET /auth/user) -func (_ Unimplemented) GetUserInfo(ctx context.Context, w *JiaozifsResponse, r *http.Request) { +// delete object. Missing objects will not return a NotFound error. +// (DELETE /object/{user}/{repository}) +func (_ Unimplemented) DeleteObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params DeleteObjectParams) { w.WriteHeader(http.StatusNotImplemented) } -// return program and runtime version -// (GET /version) -func (_ Unimplemented) GetVersion(ctx context.Context, w *JiaozifsResponse, r *http.Request) { +// get object content +// (GET /object/{user}/{repository}) +func (_ Unimplemented) GetObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params GetObjectParams) { w.WriteHeader(http.StatusNotImplemented) } -// list repository -// (GET /{user}/repository/list) -func (_ Unimplemented) ListRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string) { +// check if object exists +// (HEAD /object/{user}/{repository}) +func (_ Unimplemented) HeadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params HeadObjectParams) { w.WriteHeader(http.StatusNotImplemented) } -// create repository -// (POST /{user}/repository/new) -func (_ Unimplemented) CreateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, params CreateRepositoryParams) { +// (POST /object/{user}/{repository}) +func (_ Unimplemented) UploadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params UploadObjectParams) { w.WriteHeader(http.StatusNotImplemented) } // delete repository -// (DELETE /{user}/{repository}) +// (DELETE /repos/{user}/{repository}) func (_ Unimplemented) DeleteRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string) { w.WriteHeader(http.StatusNotImplemented) } // get repository -// (GET /{user}/{repository}) +// (GET /repos/{user}/{repository}) func (_ Unimplemented) GetRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string) { w.WriteHeader(http.StatusNotImplemented) } // update repository -// (POST /{user}/{repository}) +// (POST /repos/{user}/{repository}) func (_ Unimplemented) UpdateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateRepositoryJSONRequestBody, user string, repository string) { w.WriteHeader(http.StatusNotImplemented) } +// get commits in repository +// (GET /repos/{user}/{repository}/commits) +func (_ Unimplemented) GetCommitsInRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params GetCommitsInRepositoryParams) { + w.WriteHeader(http.StatusNotImplemented) +} + // get commit differences -// (GET /{user}/{repository}/commit/diff/{baseCommit}/{toCommit}) -func (_ Unimplemented) GetCommitDiff(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, baseCommit string, toCommit string, params GetCommitDiffParams) { +// (GET /repos/{user}/{repository}/compare/{basehead}) +func (_ Unimplemented) GetCommitDiff(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, basehead string, params GetCommitDiffParams) { w.WriteHeader(http.StatusNotImplemented) } // list entries in commit -// (GET /{user}/{repository}/commit/ls/{commitHash}) -func (_ Unimplemented) GetEntriesInCommit(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, commitHash string, params GetEntriesInCommitParams) { +// (GET /repos/{user}/{repository}/contents/) +func (_ Unimplemented) GetEntriesInCommit(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params GetEntriesInCommitParams) { w.WriteHeader(http.StatusNotImplemented) } -// delete object. Missing objects will not return a NotFound error. -// (DELETE /{user}/{repository}/object) -func (_ Unimplemented) DeleteObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params DeleteObjectParams) { +// perform a login +// (POST /users/login) +func (_ Unimplemented) Login(ctx context.Context, w *JiaozifsResponse, r *http.Request, body LoginJSONRequestBody) { w.WriteHeader(http.StatusNotImplemented) } -// get object content -// (GET /{user}/{repository}/object) -func (_ Unimplemented) GetObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params GetObjectParams) { +// perform a logout +// (POST /users/logout) +func (_ Unimplemented) Logout(ctx context.Context, w *JiaozifsResponse, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } -// check if object exists -// (HEAD /{user}/{repository}/object) -func (_ Unimplemented) HeadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params HeadObjectParams) { +// perform user registration +// (POST /users/register) +func (_ Unimplemented) Register(ctx context.Context, w *JiaozifsResponse, r *http.Request, body RegisterJSONRequestBody) { w.WriteHeader(http.StatusNotImplemented) } -// (POST /{user}/{repository}/object) -func (_ Unimplemented) UploadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params UploadObjectParams) { +// list repository +// (GET /users/repos) +func (_ Unimplemented) ListRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } -// commit working in process to branch -// (POST /{user}/{repository}/wip/commit/{refID}) -func (_ Unimplemented) CommitWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, refID openapi_types.UUID, params CommitWipParams) { +// create repository +// (POST /users/repos) +func (_ Unimplemented) CreateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateRepositoryJSONRequestBody) { w.WriteHeader(http.StatusNotImplemented) } -// list wip in specific project and user -// (GET /{user}/{repository}/wip/list) -func (_ Unimplemented) ListWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string) { +// get information of the currently logged-in user +// (GET /users/user) +func (_ Unimplemented) GetUserInfo(ctx context.Context, w *JiaozifsResponse, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// return program and runtime version +// (GET /version) +func (_ Unimplemented) GetVersion(ctx context.Context, w *JiaozifsResponse, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } // remove working in process -// (DELETE /{user}/{repository}/wip/{refID}) -func (_ Unimplemented) DeleteWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, refID openapi_types.UUID) { +// (DELETE /wip/{repository}) +func (_ Unimplemented) DeleteWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string, params DeleteWipParams) { w.WriteHeader(http.StatusNotImplemented) } // get working in process -// (GET /{user}/{repository}/wip/{refID}) -func (_ Unimplemented) GetWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, refID openapi_types.UUID) { +// (GET /wip/{repository}) +func (_ Unimplemented) GetWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string, params GetWipParams) { w.WriteHeader(http.StatusNotImplemented) } // create working in process -// (POST /{user}/{repository}/wip/{refID}) -func (_ Unimplemented) CreateWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, refID openapi_types.UUID, params CreateWipParams) { +// (POST /wip/{repository}) +func (_ Unimplemented) CreateWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string, params CreateWipParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// commit working in process to branch +// (POST /wip/{repository}/commit) +func (_ Unimplemented) CommitWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string, params CommitWipParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// list wip in specific project and user +// (GET /wip/{repository}/list) +func (_ Unimplemented) ListWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string) { w.WriteHeader(http.StatusNotImplemented) } @@ -3207,47 +3491,86 @@ type ServerInterfaceWrapper struct { type MiddlewareFunc func(http.Handler) http.Handler -// Login operation middleware -func (siw *ServerInterfaceWrapper) Login(w http.ResponseWriter, r *http.Request) { +// DeleteObject operation middleware +func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - // ------------- Body parse ------------- - var body LoginJSONRequestBody - parseBody := r.ContentLength != 0 - if parseBody { - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - http.Error(w, "Error unmarshalling body 'Login' as JSON", http.StatusBadRequest) - return - } + var err error + + // ------------- Path parameter "user" ------------- + var user string + + err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + return } - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.Login(r.Context(), &JiaozifsResponse{w}, r, body) - })) + // ------------- Path parameter "repository" ------------- + var repository string - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return } - handler.ServeHTTP(w, r.WithContext(ctx)) -} + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) -// Register operation middleware -func (siw *ServerInterfaceWrapper) Register(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) - // ------------- Body parse ------------- - var body RegisterJSONRequestBody - parseBody := r.ContentLength != 0 - if parseBody { - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - http.Error(w, "Error unmarshalling body 'Register' as JSON", http.StatusBadRequest) - return - } + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params DeleteObjectParams + + // ------------- Required query parameter "wipID" ------------- + + if paramValue := r.URL.Query().Get("wipID"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "wipID"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "wipID", r.URL.Query(), ¶ms.WipID) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "wipID", Err: err}) + return + } + + // ------------- Required query parameter "branch" ------------- + + if paramValue := r.URL.Query().Get("branch"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "branch"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "branch", r.URL.Query(), ¶ms.Branch) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "branch", Err: err}) + return + } + + // ------------- Required query parameter "path" ------------- + + if paramValue := r.URL.Query().Get("path"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.Register(r.Context(), &JiaozifsResponse{w}, r, body) + siw.Handler.DeleteObject(r.Context(), &JiaozifsResponse{w}, r, user, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -3257,31 +3580,92 @@ func (siw *ServerInterfaceWrapper) Register(w http.ResponseWriter, r *http.Reque handler.ServeHTTP(w, r.WithContext(ctx)) } -// GetUserInfo operation middleware -func (siw *ServerInterfaceWrapper) GetUserInfo(w http.ResponseWriter, r *http.Request) { +// GetObject operation middleware +func (siw *ServerInterfaceWrapper) GetObject(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + var err error - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetUserInfo(r.Context(), &JiaozifsResponse{w}, r) - })) + // ------------- Path parameter "user" ------------- + var user string - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) + err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + return } - handler.ServeHTTP(w, r.WithContext(ctx)) -} + // ------------- Path parameter "repository" ------------- + var repository string -// GetVersion operation middleware -func (siw *ServerInterfaceWrapper) GetVersion(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetObjectParams + + // ------------- Required query parameter "branch" ------------- + + if paramValue := r.URL.Query().Get("branch"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "branch"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "branch", r.URL.Query(), ¶ms.Branch) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "branch", Err: err}) + return + } + + // ------------- Required query parameter "path" ------------- + + if paramValue := r.URL.Query().Get("path"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + headers := r.Header + + // ------------- Optional header parameter "Range" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("Range")]; found { + var Range string + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Range", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "Range", valueList[0], &Range, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Range", Err: err}) + return + } + + params.Range = &Range + + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetVersion(r.Context(), &JiaozifsResponse{w}, r) + siw.Handler.GetObject(r.Context(), &JiaozifsResponse{w}, r, user, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -3291,8 +3675,8 @@ func (siw *ServerInterfaceWrapper) GetVersion(w http.ResponseWriter, r *http.Req handler.ServeHTTP(w, r.WithContext(ctx)) } -// ListRepository operation middleware -func (siw *ServerInterfaceWrapper) ListRepository(w http.ResponseWriter, r *http.Request) { +// HeadObject operation middleware +func (siw *ServerInterfaceWrapper) HeadObject(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -3306,12 +3690,77 @@ func (siw *ServerInterfaceWrapper) ListRepository(w http.ResponseWriter, r *http return } + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params HeadObjectParams + + // ------------- Required query parameter "branch" ------------- + + if paramValue := r.URL.Query().Get("branch"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "branch"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "branch", r.URL.Query(), ¶ms.Branch) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "branch", Err: err}) + return + } + + // ------------- Required query parameter "path" ------------- + + if paramValue := r.URL.Query().Get("path"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + headers := r.Header + + // ------------- Optional header parameter "Range" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("Range")]; found { + var Range string + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Range", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "Range", valueList[0], &Range, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Range", Err: err}) + return + } + + params.Range = &Range + + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.ListRepository(r.Context(), &JiaozifsResponse{w}, r, user) + siw.Handler.HeadObject(r.Context(), &JiaozifsResponse{w}, r, user, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -3321,8 +3770,8 @@ func (siw *ServerInterfaceWrapper) ListRepository(w http.ResponseWriter, r *http handler.ServeHTTP(w, r.WithContext(ctx)) } -// CreateRepository operation middleware -func (siw *ServerInterfaceWrapper) CreateRepository(w http.ResponseWriter, r *http.Request) { +// UploadObject operation middleware +func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -3336,38 +3785,92 @@ func (siw *ServerInterfaceWrapper) CreateRepository(w http.ResponseWriter, r *ht return } + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context - var params CreateRepositoryParams + var params UploadObjectParams - // ------------- Required query parameter "name" ------------- + // ------------- Required query parameter "wipID" ------------- - if paramValue := r.URL.Query().Get("name"); paramValue != "" { + if paramValue := r.URL.Query().Get("wipID"); paramValue != "" { } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "name"}) + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "wipID"}) return } - err = runtime.BindQueryParameter("form", true, true, "name", r.URL.Query(), ¶ms.Name) + err = runtime.BindQueryParameter("form", true, true, "wipID", r.URL.Query(), ¶ms.WipID) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "name", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "wipID", Err: err}) + return + } + + // ------------- Required query parameter "branch" ------------- + + if paramValue := r.URL.Query().Get("branch"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "branch"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "branch", r.URL.Query(), ¶ms.Branch) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "branch", Err: err}) return } - // ------------- Optional query parameter "description" ------------- + // ------------- Required query parameter "path" ------------- + + if paramValue := r.URL.Query().Get("path"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) + return + } - err = runtime.BindQueryParameter("form", true, false, "description", r.URL.Query(), ¶ms.Description) + err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "description", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) return } + headers := r.Header + + // ------------- Optional header parameter "If-None-Match" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("If-None-Match")]; found { + var IfNoneMatch string + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "If-None-Match", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "If-None-Match", valueList[0], &IfNoneMatch, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "If-None-Match", Err: err}) + return + } + + params.IfNoneMatch = &IfNoneMatch + + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CreateRepository(r.Context(), &JiaozifsResponse{w}, r, user, params) + siw.Handler.UploadObject(r.Context(), &JiaozifsResponse{w}, r, user, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -3405,6 +3908,8 @@ func (siw *ServerInterfaceWrapper) DeleteRepository(w http.ResponseWriter, r *ht ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.DeleteRepository(r.Context(), &JiaozifsResponse{w}, r, user, repository) })) @@ -3444,6 +3949,8 @@ func (siw *ServerInterfaceWrapper) GetRepository(w http.ResponseWriter, r *http. ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetRepository(r.Context(), &JiaozifsResponse{w}, r, user, repository) })) @@ -3493,6 +4000,8 @@ func (siw *ServerInterfaceWrapper) UpdateRepository(w http.ResponseWriter, r *ht ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.UpdateRepository(r.Context(), &JiaozifsResponse{w}, r, body, user, repository) })) @@ -3504,8 +4013,8 @@ func (siw *ServerInterfaceWrapper) UpdateRepository(w http.ResponseWriter, r *ht handler.ServeHTTP(w, r.WithContext(ctx)) } -// GetCommitDiff operation middleware -func (siw *ServerInterfaceWrapper) GetCommitDiff(w http.ResponseWriter, r *http.Request) { +// GetCommitsInRepository operation middleware +func (siw *ServerInterfaceWrapper) GetCommitsInRepository(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -3528,41 +4037,25 @@ func (siw *ServerInterfaceWrapper) GetCommitDiff(w http.ResponseWriter, r *http. return } - // ------------- Path parameter "baseCommit" ------------- - var baseCommit string - - err = runtime.BindStyledParameterWithOptions("simple", "baseCommit", chi.URLParam(r, "baseCommit"), &baseCommit, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "baseCommit", Err: err}) - return - } - - // ------------- Path parameter "toCommit" ------------- - var toCommit string - - err = runtime.BindStyledParameterWithOptions("simple", "toCommit", chi.URLParam(r, "toCommit"), &toCommit, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "toCommit", Err: err}) - return - } - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context - var params GetCommitDiffParams + var params GetCommitsInRepositoryParams - // ------------- Optional query parameter "path" ------------- + // ------------- Optional query parameter "refName" ------------- - err = runtime.BindQueryParameter("form", true, false, "path", r.URL.Query(), ¶ms.Path) + err = runtime.BindQueryParameter("form", true, false, "refName", r.URL.Query(), ¶ms.RefName) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) return } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetCommitDiff(r.Context(), &JiaozifsResponse{w}, r, user, repository, baseCommit, toCommit, params) + siw.Handler.GetCommitsInRepository(r.Context(), &JiaozifsResponse{w}, r, user, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -3572,8 +4065,8 @@ func (siw *ServerInterfaceWrapper) GetCommitDiff(w http.ResponseWriter, r *http. handler.ServeHTTP(w, r.WithContext(ctx)) } -// GetEntriesInCommit operation middleware -func (siw *ServerInterfaceWrapper) GetEntriesInCommit(w http.ResponseWriter, r *http.Request) { +// GetCommitDiff operation middleware +func (siw *ServerInterfaceWrapper) GetCommitDiff(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -3596,12 +4089,12 @@ func (siw *ServerInterfaceWrapper) GetEntriesInCommit(w http.ResponseWriter, r * return } - // ------------- Path parameter "commitHash" ------------- - var commitHash string + // ------------- Path parameter "basehead" ------------- + var basehead string - err = runtime.BindStyledParameterWithOptions("simple", "commitHash", chi.URLParam(r, "commitHash"), &commitHash, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "basehead", chi.URLParam(r, "basehead"), &basehead, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "commitHash", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "basehead", Err: err}) return } @@ -3609,8 +4102,10 @@ func (siw *ServerInterfaceWrapper) GetEntriesInCommit(w http.ResponseWriter, r * ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context - var params GetEntriesInCommitParams + var params GetCommitDiffParams // ------------- Optional query parameter "path" ------------- @@ -3621,7 +4116,7 @@ func (siw *ServerInterfaceWrapper) GetEntriesInCommit(w http.ResponseWriter, r * } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetEntriesInCommit(r.Context(), &JiaozifsResponse{w}, r, user, repository, commitHash, params) + siw.Handler.GetCommitDiff(r.Context(), &JiaozifsResponse{w}, r, user, repository, basehead, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -3631,8 +4126,8 @@ func (siw *ServerInterfaceWrapper) GetEntriesInCommit(w http.ResponseWriter, r * handler.ServeHTTP(w, r.WithContext(ctx)) } -// DeleteObject operation middleware -func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.Request) { +// GetEntriesInCommit operation middleware +func (siw *ServerInterfaceWrapper) GetEntriesInCommit(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -3659,56 +4154,29 @@ func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.R ctx = context.WithValue(ctx, Basic_authScopes, []string{}) - // Parameter object where we will unmarshal all parameters from the context - var params DeleteObjectParams - - // ------------- Required query parameter "wipID" ------------- - - if paramValue := r.URL.Query().Get("wipID"); paramValue != "" { - - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "wipID"}) - return - } - - err = runtime.BindQueryParameter("form", true, true, "wipID", r.URL.Query(), ¶ms.WipID) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "wipID", Err: err}) - return - } - - // ------------- Required query parameter "branch" ------------- + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - if paramValue := r.URL.Query().Get("branch"); paramValue != "" { + // Parameter object where we will unmarshal all parameters from the context + var params GetEntriesInCommitParams - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "branch"}) - return - } + // ------------- Optional query parameter "path" ------------- - err = runtime.BindQueryParameter("form", true, true, "branch", r.URL.Query(), ¶ms.Branch) + err = runtime.BindQueryParameter("form", true, false, "path", r.URL.Query(), ¶ms.Path) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "branch", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) return } - // ------------- Required query parameter "path" ------------- - - if paramValue := r.URL.Query().Get("path"); paramValue != "" { - - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) - return - } + // ------------- Optional query parameter "ref" ------------- - err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) + err = runtime.BindQueryParameter("form", true, false, "ref", r.URL.Query(), ¶ms.Ref) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "ref", Err: err}) return } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.DeleteObject(r.Context(), &JiaozifsResponse{w}, r, user, repository, params) + siw.Handler.GetEntriesInCommit(r.Context(), &JiaozifsResponse{w}, r, user, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -3718,90 +4186,74 @@ func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.R handler.ServeHTTP(w, r.WithContext(ctx)) } -// GetObject operation middleware -func (siw *ServerInterfaceWrapper) GetObject(w http.ResponseWriter, r *http.Request) { +// Login operation middleware +func (siw *ServerInterfaceWrapper) Login(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - var err error + // ------------- Body parse ------------- + var body LoginJSONRequestBody + parseBody := r.ContentLength != 0 + if parseBody { + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "Error unmarshalling body 'Login' as JSON", http.StatusBadRequest) + return + } + } - // ------------- Path parameter "user" ------------- - var user string + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.Login(r.Context(), &JiaozifsResponse{w}, r, body) + })) - err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) - return + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) } - // ------------- Path parameter "repository" ------------- - var repository string + handler.ServeHTTP(w, r.WithContext(ctx)) +} - err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) - return - } +// Logout operation middleware +func (siw *ServerInterfaceWrapper) Logout(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) ctx = context.WithValue(ctx, Basic_authScopes, []string{}) - // Parameter object where we will unmarshal all parameters from the context - var params GetObjectParams - - // ------------- Required query parameter "branch" ------------- - - if paramValue := r.URL.Query().Get("branch"); paramValue != "" { - - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "branch"}) - return - } - - err = runtime.BindQueryParameter("form", true, true, "branch", r.URL.Query(), ¶ms.Branch) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "branch", Err: err}) - return - } - - // ------------- Required query parameter "path" ------------- + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - if paramValue := r.URL.Query().Get("path"); paramValue != "" { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.Logout(r.Context(), &JiaozifsResponse{w}, r) + })) - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) - return + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) } - err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) - return - } + handler.ServeHTTP(w, r.WithContext(ctx)) +} - headers := r.Header +// Register operation middleware +func (siw *ServerInterfaceWrapper) Register(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() - // ------------- Optional header parameter "Range" ------------- - if valueList, found := headers[http.CanonicalHeaderKey("Range")]; found { - var Range string - n := len(valueList) - if n != 1 { - siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Range", Count: n}) + // ------------- Body parse ------------- + var body RegisterJSONRequestBody + parseBody := r.ContentLength != 0 + if parseBody { + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "Error unmarshalling body 'Register' as JSON", http.StatusBadRequest) return } + } - err = runtime.BindStyledParameterWithOptions("simple", "Range", valueList[0], &Range, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Range", Err: err}) - return - } + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - params.Range = &Range + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) - } + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetObject(r.Context(), &JiaozifsResponse{w}, r, user, repository, params) + siw.Handler.Register(r.Context(), &JiaozifsResponse{w}, r, body) })) for _, middleware := range siw.HandlerMiddlewares { @@ -3811,90 +4263,91 @@ func (siw *ServerInterfaceWrapper) GetObject(w http.ResponseWriter, r *http.Requ handler.ServeHTTP(w, r.WithContext(ctx)) } -// HeadObject operation middleware -func (siw *ServerInterfaceWrapper) HeadObject(w http.ResponseWriter, r *http.Request) { +// ListRepository operation middleware +func (siw *ServerInterfaceWrapper) ListRepository(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - var err error + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - // ------------- Path parameter "user" ------------- - var user string + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) - err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) - return + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListRepository(r.Context(), &JiaozifsResponse{w}, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) } - // ------------- Path parameter "repository" ------------- - var repository string + handler.ServeHTTP(w, r.WithContext(ctx)) +} - err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) - return +// CreateRepository operation middleware +func (siw *ServerInterfaceWrapper) CreateRepository(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + // ------------- Body parse ------------- + var body CreateRepositoryJSONRequestBody + parseBody := true + if parseBody { + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "Error unmarshalling body 'CreateRepository' as JSON", http.StatusBadRequest) + return + } } ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) ctx = context.WithValue(ctx, Basic_authScopes, []string{}) - // Parameter object where we will unmarshal all parameters from the context - var params HeadObjectParams - - // ------------- Required query parameter "branch" ------------- + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - if paramValue := r.URL.Query().Get("branch"); paramValue != "" { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CreateRepository(r.Context(), &JiaozifsResponse{w}, r, body) + })) - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "branch"}) - return + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) } - err = runtime.BindQueryParameter("form", true, true, "branch", r.URL.Query(), ¶ms.Branch) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "branch", Err: err}) - return - } + handler.ServeHTTP(w, r.WithContext(ctx)) +} - // ------------- Required query parameter "path" ------------- +// GetUserInfo operation middleware +func (siw *ServerInterfaceWrapper) GetUserInfo(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() - if paramValue := r.URL.Query().Get("path"); paramValue != "" { + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) - return - } + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) - err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) - return + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetUserInfo(r.Context(), &JiaozifsResponse{w}, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) } - headers := r.Header + handler.ServeHTTP(w, r.WithContext(ctx)) +} - // ------------- Optional header parameter "Range" ------------- - if valueList, found := headers[http.CanonicalHeaderKey("Range")]; found { - var Range string - n := len(valueList) - if n != 1 { - siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Range", Count: n}) - return - } +// GetVersion operation middleware +func (siw *ServerInterfaceWrapper) GetVersion(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() - err = runtime.BindStyledParameterWithOptions("simple", "Range", valueList[0], &Range, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Range", Err: err}) - return - } + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - params.Range = &Range + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) - } + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.HeadObject(r.Context(), &JiaozifsResponse{w}, r, user, repository, params) + siw.Handler.GetVersion(r.Context(), &JiaozifsResponse{w}, r) })) for _, middleware := range siw.HandlerMiddlewares { @@ -3904,21 +4357,12 @@ func (siw *ServerInterfaceWrapper) HeadObject(w http.ResponseWriter, r *http.Req handler.ServeHTTP(w, r.WithContext(ctx)) } -// UploadObject operation middleware -func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.Request) { +// DeleteWip operation middleware +func (siw *ServerInterfaceWrapper) DeleteWip(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error - // ------------- Path parameter "user" ------------- - var user string - - err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) - return - } - // ------------- Path parameter "repository" ------------- var repository string @@ -3932,77 +4376,28 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R ctx = context.WithValue(ctx, Basic_authScopes, []string{}) - // Parameter object where we will unmarshal all parameters from the context - var params UploadObjectParams - - // ------------- Required query parameter "wipID" ------------- - - if paramValue := r.URL.Query().Get("wipID"); paramValue != "" { - - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "wipID"}) - return - } - - err = runtime.BindQueryParameter("form", true, true, "wipID", r.URL.Query(), ¶ms.WipID) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "wipID", Err: err}) - return - } - - // ------------- Required query parameter "branch" ------------- - - if paramValue := r.URL.Query().Get("branch"); paramValue != "" { - - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "branch"}) - return - } + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - err = runtime.BindQueryParameter("form", true, true, "branch", r.URL.Query(), ¶ms.Branch) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "branch", Err: err}) - return - } + // Parameter object where we will unmarshal all parameters from the context + var params DeleteWipParams - // ------------- Required query parameter "path" ------------- + // ------------- Required query parameter "refName" ------------- - if paramValue := r.URL.Query().Get("path"); paramValue != "" { + if paramValue := r.URL.Query().Get("refName"); paramValue != "" { } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) return } - err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) + err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) return } - headers := r.Header - - // ------------- Optional header parameter "If-None-Match" ------------- - if valueList, found := headers[http.CanonicalHeaderKey("If-None-Match")]; found { - var IfNoneMatch string - n := len(valueList) - if n != 1 { - siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "If-None-Match", Count: n}) - return - } - - err = runtime.BindStyledParameterWithOptions("simple", "If-None-Match", valueList[0], &IfNoneMatch, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "If-None-Match", Err: err}) - return - } - - params.IfNoneMatch = &IfNoneMatch - - } - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.UploadObject(r.Context(), &JiaozifsResponse{w}, r, user, repository, params) + siw.Handler.DeleteWip(r.Context(), &JiaozifsResponse{w}, r, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -4012,21 +4407,12 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R handler.ServeHTTP(w, r.WithContext(ctx)) } -// CommitWip operation middleware -func (siw *ServerInterfaceWrapper) CommitWip(w http.ResponseWriter, r *http.Request) { +// GetWip operation middleware +func (siw *ServerInterfaceWrapper) GetWip(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error - // ------------- Path parameter "user" ------------- - var user string - - err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) - return - } - // ------------- Path parameter "repository" ------------- var repository string @@ -4036,32 +4422,32 @@ func (siw *ServerInterfaceWrapper) CommitWip(w http.ResponseWriter, r *http.Requ return } - // ------------- Path parameter "refID" ------------- - var refID openapi_types.UUID - - err = runtime.BindStyledParameterWithOptions("simple", "refID", chi.URLParam(r, "refID"), &refID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refID", Err: err}) - return - } - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context - var params CommitWipParams + var params GetWipParams - // ------------- Optional query parameter "msg" ------------- + // ------------- Required query parameter "refName" ------------- - err = runtime.BindQueryParameter("form", true, false, "msg", r.URL.Query(), ¶ms.Msg) + if paramValue := r.URL.Query().Get("refName"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "msg", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) return } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CommitWip(r.Context(), &JiaozifsResponse{w}, r, user, repository, refID, params) + siw.Handler.GetWip(r.Context(), &JiaozifsResponse{w}, r, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -4071,21 +4457,12 @@ func (siw *ServerInterfaceWrapper) CommitWip(w http.ResponseWriter, r *http.Requ handler.ServeHTTP(w, r.WithContext(ctx)) } -// ListWip operation middleware -func (siw *ServerInterfaceWrapper) ListWip(w http.ResponseWriter, r *http.Request) { +// CreateWip operation middleware +func (siw *ServerInterfaceWrapper) CreateWip(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error - // ------------- Path parameter "user" ------------- - var user string - - err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) - return - } - // ------------- Path parameter "repository" ------------- var repository string @@ -4099,56 +4476,58 @@ func (siw *ServerInterfaceWrapper) ListWip(w http.ResponseWriter, r *http.Reques ctx = context.WithValue(ctx, Basic_authScopes, []string{}) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.ListWip(r.Context(), &JiaozifsResponse{w}, r, user, repository) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - handler.ServeHTTP(w, r.WithContext(ctx)) -} + // Parameter object where we will unmarshal all parameters from the context + var params CreateWipParams -// DeleteWip operation middleware -func (siw *ServerInterfaceWrapper) DeleteWip(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + // ------------- Required query parameter "name" ------------- - var err error + if paramValue := r.URL.Query().Get("name"); paramValue != "" { - // ------------- Path parameter "user" ------------- - var user string + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "name"}) + return + } - err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindQueryParameter("form", true, true, "name", r.URL.Query(), ¶ms.Name) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "name", Err: err}) return } - // ------------- Path parameter "repository" ------------- - var repository string + // ------------- Required query parameter "baseCommitID" ------------- - err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + if paramValue := r.URL.Query().Get("baseCommitID"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "baseCommitID"}) return } - // ------------- Path parameter "refID" ------------- - var refID openapi_types.UUID - - err = runtime.BindStyledParameterWithOptions("simple", "refID", chi.URLParam(r, "refID"), &refID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindQueryParameter("form", true, true, "baseCommitID", r.URL.Query(), ¶ms.BaseCommitID) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refID", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "baseCommitID", Err: err}) return } - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + // ------------- Required query parameter "refName" ------------- - ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + if paramValue := r.URL.Query().Get("refName"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) + return + } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.DeleteWip(r.Context(), &JiaozifsResponse{w}, r, user, repository, refID) + siw.Handler.CreateWip(r.Context(), &JiaozifsResponse{w}, r, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -4158,21 +4537,12 @@ func (siw *ServerInterfaceWrapper) DeleteWip(w http.ResponseWriter, r *http.Requ handler.ServeHTTP(w, r.WithContext(ctx)) } -// GetWip operation middleware -func (siw *ServerInterfaceWrapper) GetWip(w http.ResponseWriter, r *http.Request) { +// CommitWip operation middleware +func (siw *ServerInterfaceWrapper) CommitWip(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error - // ------------- Path parameter "user" ------------- - var user string - - err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) - return - } - // ------------- Path parameter "repository" ------------- var repository string @@ -4182,21 +4552,40 @@ func (siw *ServerInterfaceWrapper) GetWip(w http.ResponseWriter, r *http.Request return } - // ------------- Path parameter "refID" ------------- - var refID openapi_types.UUID + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params CommitWipParams + + // ------------- Optional query parameter "msg" ------------- - err = runtime.BindStyledParameterWithOptions("simple", "refID", chi.URLParam(r, "refID"), &refID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindQueryParameter("form", true, false, "msg", r.URL.Query(), ¶ms.Msg) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refID", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "msg", Err: err}) return } - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + // ------------- Required query parameter "refName" ------------- - ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + if paramValue := r.URL.Query().Get("refName"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) + return + } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetWip(r.Context(), &JiaozifsResponse{w}, r, user, repository, refID) + siw.Handler.CommitWip(r.Context(), &JiaozifsResponse{w}, r, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -4206,21 +4595,12 @@ func (siw *ServerInterfaceWrapper) GetWip(w http.ResponseWriter, r *http.Request handler.ServeHTTP(w, r.WithContext(ctx)) } -// CreateWip operation middleware -func (siw *ServerInterfaceWrapper) CreateWip(w http.ResponseWriter, r *http.Request) { +// ListWip operation middleware +func (siw *ServerInterfaceWrapper) ListWip(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error - // ------------- Path parameter "user" ------------- - var user string - - err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) - return - } - // ------------- Path parameter "repository" ------------- var repository string @@ -4230,54 +4610,14 @@ func (siw *ServerInterfaceWrapper) CreateWip(w http.ResponseWriter, r *http.Requ return } - // ------------- Path parameter "refID" ------------- - var refID openapi_types.UUID - - err = runtime.BindStyledParameterWithOptions("simple", "refID", chi.URLParam(r, "refID"), &refID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refID", Err: err}) - return - } - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) ctx = context.WithValue(ctx, Basic_authScopes, []string{}) - // Parameter object where we will unmarshal all parameters from the context - var params CreateWipParams - - // ------------- Required query parameter "name" ------------- - - if paramValue := r.URL.Query().Get("name"); paramValue != "" { - - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "name"}) - return - } - - err = runtime.BindQueryParameter("form", true, true, "name", r.URL.Query(), ¶ms.Name) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "name", Err: err}) - return - } - - // ------------- Required query parameter "baseCommitID" ------------- - - if paramValue := r.URL.Query().Get("baseCommitID"); paramValue != "" { - - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "baseCommitID"}) - return - } - - err = runtime.BindQueryParameter("form", true, true, "baseCommitID", r.URL.Query(), ¶ms.BaseCommitID) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "baseCommitID", Err: err}) - return - } + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CreateWip(r.Context(), &JiaozifsResponse{w}, r, user, repository, refID, params) + siw.Handler.ListWip(r.Context(), &JiaozifsResponse{w}, r, repository) })) for _, middleware := range siw.HandlerMiddlewares { @@ -4356,6 +4696,11 @@ func (e *TooManyValuesForParamError) Error() string { return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) } +// RawSpec hack to get swagger json for swagger ui +func RawSpec() ([]byte, error) { + return rawSpec() +} + // Handler creates http.Handler with routing matching OpenAPI spec. func Handler(si ServerInterface) http.Handler { return HandlerWithOptions(si, ChiServerOptions{}) @@ -4401,64 +4746,70 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl } r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/auth/login", wrapper.Login) + r.Delete(options.BaseURL+"/object/{user}/{repository}", wrapper.DeleteObject) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/auth/register", wrapper.Register) + r.Get(options.BaseURL+"/object/{user}/{repository}", wrapper.GetObject) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/auth/user", wrapper.GetUserInfo) + r.Head(options.BaseURL+"/object/{user}/{repository}", wrapper.HeadObject) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/version", wrapper.GetVersion) + r.Post(options.BaseURL+"/object/{user}/{repository}", wrapper.UploadObject) + }) + r.Group(func(r chi.Router) { + r.Delete(options.BaseURL+"/repos/{user}/{repository}", wrapper.DeleteRepository) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/repos/{user}/{repository}", wrapper.GetRepository) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/{user}/repository/list", wrapper.ListRepository) + r.Post(options.BaseURL+"/repos/{user}/{repository}", wrapper.UpdateRepository) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/{user}/repository/new", wrapper.CreateRepository) + r.Get(options.BaseURL+"/repos/{user}/{repository}/commits", wrapper.GetCommitsInRepository) }) r.Group(func(r chi.Router) { - r.Delete(options.BaseURL+"/{user}/{repository}", wrapper.DeleteRepository) + r.Get(options.BaseURL+"/repos/{user}/{repository}/compare/{basehead}", wrapper.GetCommitDiff) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/{user}/{repository}", wrapper.GetRepository) + r.Get(options.BaseURL+"/repos/{user}/{repository}/contents/", wrapper.GetEntriesInCommit) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/{user}/{repository}", wrapper.UpdateRepository) + r.Post(options.BaseURL+"/users/login", wrapper.Login) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/{user}/{repository}/commit/diff/{baseCommit}/{toCommit}", wrapper.GetCommitDiff) + r.Post(options.BaseURL+"/users/logout", wrapper.Logout) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/{user}/{repository}/commit/ls/{commitHash}", wrapper.GetEntriesInCommit) + r.Post(options.BaseURL+"/users/register", wrapper.Register) }) r.Group(func(r chi.Router) { - r.Delete(options.BaseURL+"/{user}/{repository}/object", wrapper.DeleteObject) + r.Get(options.BaseURL+"/users/repos", wrapper.ListRepository) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/{user}/{repository}/object", wrapper.GetObject) + r.Post(options.BaseURL+"/users/repos", wrapper.CreateRepository) }) r.Group(func(r chi.Router) { - r.Head(options.BaseURL+"/{user}/{repository}/object", wrapper.HeadObject) + r.Get(options.BaseURL+"/users/user", wrapper.GetUserInfo) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/{user}/{repository}/object", wrapper.UploadObject) + r.Get(options.BaseURL+"/version", wrapper.GetVersion) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/{user}/{repository}/wip/commit/{refID}", wrapper.CommitWip) + r.Delete(options.BaseURL+"/wip/{repository}", wrapper.DeleteWip) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/{user}/{repository}/wip/list", wrapper.ListWip) + r.Get(options.BaseURL+"/wip/{repository}", wrapper.GetWip) }) r.Group(func(r chi.Router) { - r.Delete(options.BaseURL+"/{user}/{repository}/wip/{refID}", wrapper.DeleteWip) + r.Post(options.BaseURL+"/wip/{repository}", wrapper.CreateWip) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/{user}/{repository}/wip/{refID}", wrapper.GetWip) + r.Post(options.BaseURL+"/wip/{repository}/commit", wrapper.CommitWip) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/{user}/{repository}/wip/{refID}", wrapper.CreateWip) + r.Get(options.BaseURL+"/wip/{repository}/list", wrapper.ListWip) }) return r @@ -4467,52 +4818,55 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", "AA==", } diff --git a/api/swagger.yml b/api/swagger.yml index e053f172..19232c35 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -11,10 +11,12 @@ info: servers: - url: "http://localhost:34913/api/v1" description: jiaozifs server endpoint - + - url: "/api/v1" + description: jiaozifs server endpoint security: - jwt_token: [] - basic_auth: [] + - cookie_auth: [] components: securitySchemes: basic_auth: @@ -28,15 +30,16 @@ components: type: apiKey in: cookie name: internal_auth_session - oidc_auth: - type: apiKey - in: cookie - name: oidc_auth_session - saml_auth: - type: apiKey - in: cookie - name: saml_auth_session schemas: + CreateRepository: + type: object + required: + - Name + properties: + Description: + type: string + Name: + type: string UpdateRepository: type: object properties: @@ -44,6 +47,13 @@ components: type: string Repository: type: object + required: + - ID + - Name + - Head + - CreatorID + - CreatedAt + - UpdatedAt properties: ID: type: string @@ -65,6 +75,12 @@ components: format: date-time Blob: type: object + required: + - Hash + - Type + - Size + - CreatedAt + - UpdatedAt properties: Hash: type: string @@ -83,18 +99,33 @@ components: Signature: type: object + required: + - Name + - Email + - When properties: Name: type: string Email: type: string format: email - Timestamp: + When: type: string format: date-time Commit: type: object + required: + - Hash + - Type + - Author + - Committer + - MergeTag + - Message + - TreeHash + - ParentHashes + - CreatedAt + - UpdatedAt properties: Hash: type: string @@ -362,8 +393,6 @@ paths: - common operationId: getVersion summary: return program and runtime version - security: - - jwt_token: [] responses: 200: description: program version @@ -372,7 +401,7 @@ paths: schema: $ref: "#/components/schemas/VersionResult" - /{user}/{repository}/object: + /object/{user}/{repository}: parameters: - in: path name: user @@ -607,24 +636,19 @@ paths: 420: description: too many requests - /{user}/{repository}/wip/{refID}: + /wip/{repository}: parameters: - - in: path - name: user - required: true - schema: - type: string - in: path name: repository required: true schema: type: string - - in: path - name: refID + - in: query + name: refName + description: ref name required: true schema: type: string - format: uuid get: tags: - wip @@ -691,24 +715,19 @@ paths: 502: description: internal server error - /{user}/{repository}/wip/commit/{refID}: + /wip/{repository}/commit: parameters: - - in: path - name: user - required: true - schema: - type: string - in: path name: repository required: true schema: type: string - - in: path - name: refID + - in: query + name: refName + description: ref name required: true schema: type: string - format: uuid post: tags: - wip @@ -737,13 +756,8 @@ paths: 502: description: internal server error - /{user}/{repository}/wip/list: + /wip/{repository}/list: parameters: - - in: path - name: user - required: true - schema: - type: string - in: path name: repository required: true @@ -770,7 +784,7 @@ paths: 403: description: Forbidden - /{user}/{repository}/commit/ls/{commitHash}: + /repos/{user}/{repository}/contents/: parameters: - in: path name: user @@ -782,11 +796,6 @@ paths: required: true schema: type: string - - in: path - name: commitHash - required: true - schema: - type: string get: tags: - commit @@ -799,6 +808,12 @@ paths: required: false schema: type: string + - in: query + name: ref + description: specific ref + required: false + schema: + type: string responses: 200: description: commit @@ -817,7 +832,7 @@ paths: 404: description: url not found - /{user}/{repository}/commit/diff/{baseCommit}/{toCommit}: + /repos/{user}/{repository}/compare/{basehead}: parameters: - in: path name: user @@ -830,12 +845,7 @@ paths: schema: type: string - in: path - name: baseCommit - required: true - schema: - type: string - - in: path - name: toCommit + name: basehead required: true schema: type: string @@ -867,49 +877,41 @@ paths: 503: description: server internal error - - /{user}/repository/new: + /repos/{user}/{repository}/commits: parameters: - in: path name: user required: true schema: type: string - post: + - in: path + name: repository + required: true + schema: + type: string + get: tags: - repo - operationId: createRepository - summary: create repository + operationId: getCommitsInRepository + summary: get commits in repository parameters: - in: query - name: name - description: repository name - required: true - schema: - type: string - - in: query - name: description - description: repository description + name: refName + description: ref(branch/tag) name required: false schema: type: string responses: - 201: - description: new repository + 200: + description: get commits content: application/json: schema: type: array items: - $ref: "#/components/schemas/Repository" - 400: - description: ValidationError - 401: - description: Unauthorized - 403: - description: Forbidden + $ref: "#/components/schemas/Commit" - /{user}/{repository}: + /repos/{user}/{repository}: parameters: - in: path name: user @@ -974,13 +976,8 @@ paths: 403: description: Forbidden - /{user}/repository/list: - parameters: - - in: path - name: user - required: true - schema: - type: string +# 必须授权 + /users/repos: get: tags: - repo @@ -1001,8 +998,34 @@ paths: description: Unauthorized 403: description: Forbidden + post: + tags: + - repo + operationId: createRepository + summary: create repository + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/CreateRepository" + responses: + 201: + description: new repository + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Repository" + 400: + description: ValidationError + 401: + description: Unauthorized + 403: + description: Forbidden - /auth/login: + /users/login: post: tags: - auth @@ -1029,7 +1052,7 @@ paths: Set-Cookie: schema: type: string - example: "access_token=abcde12356; Path=/; HttpOnly" + example: "internal_auth_session=abcde12356; Path=/; HttpOnly" content: application/json: schema: @@ -1041,13 +1064,28 @@ paths: default: description: Internal Server Error - /auth/register: + /users/logout: + post: + tags: + - auth + operationId: logout + summary: perform a logout + responses: + 200: + description: successful logout + 401: + description: Unauthorized ValidationError + 420: + description: too many requests + default: + description: Internal Server Error + + /users/register: post: tags: - auth operationId: register summary: perform user registration - security: [] # No authentication requestBody: content: application/json: @@ -1063,14 +1101,12 @@ paths: default: description: Internal Server Error - /auth/user: + /users/user: get: tags: - auth operationId: getUserInfo summary: get information of the currently logged-in user - security: - - jwt_token: [] responses: 200: description: Successful get of user Info diff --git a/api/tmpls/chi/chi-middleware.tmpl b/api/tmpls/chi/chi-middleware.tmpl index 10c7ed54..833717f0 100644 --- a/api/tmpls/chi/chi-middleware.tmpl +++ b/api/tmpls/chi/chi-middleware.tmpl @@ -270,3 +270,9 @@ type TooManyValuesForParamError struct { func (e *TooManyValuesForParamError) Error() string { return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) } + + +// RawSpec hack to get swagger json for swagger ui +func RawSpec() ([]byte, error) { + return rawSpec() +} \ No newline at end of file diff --git a/auth/auth_middleware.go b/auth/auth_middleware.go index 3b4b27cf..6a51128c 100644 --- a/auth/auth_middleware.go +++ b/auth/auth_middleware.go @@ -1,31 +1,33 @@ -package api +package auth import ( "context" "errors" - "fmt" "net/http" "strings" - "time" + + "github.com/jiaozifs/jiaozifs/auth/crypt" + + "github.com/gorilla/sessions" + "github.com/jiaozifs/jiaozifs/models" "github.com/getkin/kin-openapi/openapi3" "github.com/getkin/kin-openapi/routers" "github.com/getkin/kin-openapi/routers/legacy" - "github.com/gorilla/sessions" - "github.com/treeverse/lakefs/pkg/api/apigen" - "github.com/treeverse/lakefs/pkg/auth" - "github.com/treeverse/lakefs/pkg/auth/model" - oidcencoding "github.com/treeverse/lakefs/pkg/auth/oidc/encoding" - "github.com/treeverse/lakefs/pkg/logging" ) const ( - TokenSessionKeyName = "token" - InternalAuthSessionName = "internal_auth_session" - IDTokenClaimsSessionKey = "id_token_claims" - OIDCAuthSessionName = "oidc_auth_session" - SAMLTokenClaimsSessionKey = "saml_token_claims" - SAMLAuthSessionName = "saml_auth_session" + TokenSessionKeyName = "token" + InternalAuthSessionName = "internal_auth_session" + IDTokenClaimsSessionKey = "id_token_claims" +) + +var ( + ErrFailedToAccessStorage = errors.New("failed to access storage") + ErrAuthenticatingRequest = errors.New("error authenticating request") + ErrInvalidAPIEndpoint = errors.New("invalid API endpoint") + ErrRequestSizeExceeded = errors.New("request size exceeded") + ErrStorageNamespaceInUse = errors.New("storage namespace already in use") ) // extractSecurityRequirements using Swagger returns an array of security requirements set for the request. @@ -36,18 +38,11 @@ func extractSecurityRequirements(router routers.Router, r *http.Request) (openap return nil, err } if route.Operation.Security == nil { - return route.Swagger.Security, nil + return route.Spec.Security, nil } return *route.Operation.Security, nil } -type OIDCConfig struct { - ValidateIDTokenClaims map[string]string - DefaultInitialGroups []string - InitialGroupsClaimName string - FriendlyNameClaimName string -} - type CookieAuthConfig struct { ValidateIDTokenClaims map[string]string DefaultInitialGroups []string @@ -57,29 +52,7 @@ type CookieAuthConfig struct { AuthSource string } -func GenericAuthMiddleware(logger logging.Logger, authenticator auth.Authenticator, authService auth.Service, oidcConfig *OIDCConfig, cookieAuthConfig *CookieAuthConfig) (func(next http.Handler) http.Handler, error) { - swagger, err := apigen.GetSwagger() - if err != nil { - return nil, err - } - sessionStore := sessions.NewCookieStore(authService.SecretStore().SharedSecret()) - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - user, err := checkSecurityRequirements(r, swagger.Security, logger, authenticator, authService, sessionStore, oidcConfig, cookieAuthConfig) - if err != nil { - writeError(w, r, http.StatusUnauthorized, err) - return - } - if user != nil { - ctx := logging.AddFields(r.Context(), logging.Fields{logging.UserFieldKey: user.Username}) - r = r.WithContext(auth.WithUser(ctx, user)) - } - next.ServeHTTP(w, r) - }) - }, nil -} - -func AuthMiddleware(logger logging.Logger, swagger *openapi3.Swagger, authenticator auth.Authenticator, authService auth.Service, sessionStore sessions.Store, oidcConfig *OIDCConfig, cookieAuthConfig *CookieAuthConfig) func(next http.Handler) http.Handler { +func Middleware(swagger *openapi3.T, authenticator Authenticator, secretStore crypt.SecretStore, userRepo models.IUserRepo, sessionStore sessions.Store) func(next http.Handler) http.Handler { router, err := legacy.NewRouter(swagger) if err != nil { panic(err) @@ -87,23 +60,25 @@ func AuthMiddleware(logger logging.Logger, swagger *openapi3.Swagger, authentica return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // if request already authenticated - if _, userNotFoundErr := auth.GetUser(r.Context()); userNotFoundErr == nil { + if _, userNotFoundErr := GetUser(r.Context()); userNotFoundErr == nil { next.ServeHTTP(w, r) return } + securityRequirements, err := extractSecurityRequirements(router, r) if err != nil { - writeError(w, r, http.StatusBadRequest, err) + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(err.Error())) return } - user, err := checkSecurityRequirements(r, securityRequirements, logger, authenticator, authService, sessionStore, oidcConfig, cookieAuthConfig) + user, err := checkSecurityRequirements(r, securityRequirements, authenticator, sessionStore, secretStore, userRepo) if err != nil { - writeError(w, r, http.StatusUnauthorized, err) + w.WriteHeader(http.StatusUnauthorized) + _, _ = w.Write([]byte(err.Error())) return } if user != nil { - ctx := logging.AddFields(r.Context(), logging.Fields{logging.UserFieldKey: user.Username}) - r = r.WithContext(auth.WithUser(ctx, user)) + r = r.WithContext(WithUser(r.Context(), user)) } next.ServeHTTP(w, r) }) @@ -114,19 +89,15 @@ func AuthMiddleware(logger logging.Logger, swagger *openapi3.Swagger, authentica // it will return nil user and error in case of no security checks to match. func checkSecurityRequirements(r *http.Request, securityRequirements openapi3.SecurityRequirements, - logger logging.Logger, - authenticator auth.Authenticator, - authService auth.Service, + authenticator Authenticator, sessionStore sessions.Store, - oidcConfig *OIDCConfig, - cookieAuthConfig *CookieAuthConfig, -) (*model.User, error) { + secretStore crypt.SecretStore, + userRepo models.IUserRepo, +) (*models.User, error) { ctx := r.Context() - var user *model.User + var user *models.User var err error - logger = logger.WithContext(ctx) - for _, securityRequirement := range securityRequirements { for provider := range securityRequirement { switch provider { @@ -141,14 +112,14 @@ func checkSecurityRequirements(r *http.Request, continue } token := parts[1] - user, err = userByToken(ctx, logger, authService, token) + user, err = userByToken(ctx, secretStore, userRepo, token) case "basic_auth": // validate using basic auth accessKey, secretKey, ok := r.BasicAuth() if !ok { continue } - user, err = userByAuth(ctx, logger, authenticator, authService, accessKey, secretKey) + user, err = userByAuth(ctx, authenticator, userRepo, accessKey, secretKey) case "cookie_auth": var internalAuthSession *sessions.Session internalAuthSession, _ = sessionStore.Get(r, InternalAuthSessionName) @@ -159,24 +130,10 @@ func checkSecurityRequirements(r *http.Request, if token == "" { continue } - user, err = userByToken(ctx, logger, authService, token) - case "oidc_auth": - var oidcSession *sessions.Session - oidcSession, err = sessionStore.Get(r, OIDCAuthSessionName) - if err != nil { - return nil, err - } - user, err = userFromOIDC(ctx, logger, authService, oidcSession, oidcConfig) - case "saml_auth": - var samlSession *sessions.Session - samlSession, err = sessionStore.Get(r, SAMLAuthSessionName) - if err != nil { - return nil, err - } - user, err = userFromSAML(ctx, logger, authService, samlSession, cookieAuthConfig) + user, err = userByToken(ctx, secretStore, userRepo, token) default: // unknown security requirement to check - logger.WithField("provider", provider).Error("Authentication middleware unknown security requirement provider") + log.With("provider", provider).Error("Authentication middleware unknown security requirement provider") return nil, ErrAuthenticatingRequest } @@ -191,205 +148,35 @@ func checkSecurityRequirements(r *http.Request, return nil, nil } -func enhanceWithFriendlyName(user *model.User, friendlyName string) *model.User { - if friendlyName != "" { - user.FriendlyName = &friendlyName - } - return user -} - -// userFromSAML returns a user from an existing SAML session. -// If the user doesn't exist on the lakeFS side, it is created. -// This function does not make any calls to an external provider. -func userFromSAML(ctx context.Context, logger logging.Logger, authService auth.Service, authSession *sessions.Session, cookieAuthConfig *CookieAuthConfig) (*model.User, error) { - idTokenClaims, ok := authSession.Values[SAMLTokenClaimsSessionKey].(oidcencoding.Claims) - if idTokenClaims == nil { - return nil, nil - } - if !ok { - logger.WithField("claims", authSession.Values[SAMLTokenClaimsSessionKey]).Debug("failed decoding tokens") - return nil, fmt.Errorf("getting token claims: %w", ErrAuthenticatingRequest) - } - logger.WithField("claims", idTokenClaims).Debug("Success decoding token claims") - - idKey := cookieAuthConfig.ExternalUserIDClaimName - externalID, ok := idTokenClaims[idKey].(string) - if !ok { - logger.WithField(idKey, idTokenClaims[idKey]).Error("Failed type assertion for sub claim") - return nil, ErrAuthenticatingRequest - } - - log := logger.WithField("external_id", externalID) - - for claimName, expectedValue := range cookieAuthConfig.ValidateIDTokenClaims { - actualValue, ok := idTokenClaims[claimName] - if !ok || actualValue != expectedValue { - log.WithFields(logging.Fields{ - "claim_name": claimName, - "actual_value": actualValue, - "expected_value": expectedValue, - "missing": !ok, - }).Error("authentication failed on validating ID token claims") - return nil, ErrAuthenticatingRequest - } - } - - // update user - // TODO(isan) consolidate userFromOIDC and userFromSAML below here internal db handling code - friendlyName := "" - if cookieAuthConfig.FriendlyNameClaimName != "" { - friendlyName, _ = idTokenClaims[cookieAuthConfig.FriendlyNameClaimName].(string) - } - log = log.WithField("friendly_name", friendlyName) - - user, err := authService.GetUserByExternalID(ctx, externalID) - if err == nil { - log.Info("Found user") - return enhanceWithFriendlyName(user, friendlyName), nil - } - if !errors.Is(err, auth.ErrNotFound) { - log.WithError(err).Error("Failed while searching if user exists in database") - return nil, ErrAuthenticatingRequest - } - log.Info("User not found; creating them") - - u := model.User{ - CreatedAt: time.Now().UTC(), - Source: cookieAuthConfig.AuthSource, - Username: externalID, - ExternalID: &externalID, - } - - _, err = authService.CreateUser(ctx, &u) - if err != nil { - if !errors.Is(err, auth.ErrAlreadyExists) { - log.WithError(err).Error("Failed to create external user in database") - return nil, ErrAuthenticatingRequest - } - // user already exists - get it: - user, err = authService.GetUserByExternalID(ctx, externalID) - if err != nil { - log.WithError(err).Error("failed to get external user from database") - return nil, ErrAuthenticatingRequest - } - return enhanceWithFriendlyName(user, friendlyName), nil - } - initialGroups := cookieAuthConfig.DefaultInitialGroups - if userInitialGroups, ok := idTokenClaims[cookieAuthConfig.InitialGroupsClaimName].(string); ok { - initialGroups = strings.Split(userInitialGroups, ",") - } - for _, g := range initialGroups { - err = authService.AddUserToGroup(ctx, u.Username, strings.TrimSpace(g)) - if err != nil { - log.WithError(err).Error("Failed to add external user to group") - } - } - return enhanceWithFriendlyName(&u, friendlyName), nil -} - -// userFromOIDC returns a user from an existing OIDC session. -// If the user doesn't exist on the lakeFS side, it is created. -// This function does not make any calls to an external provider. -func userFromOIDC(ctx context.Context, logger logging.Logger, authService auth.Service, authSession *sessions.Session, oidcConfig *OIDCConfig) (*model.User, error) { - idTokenClaims, ok := authSession.Values[IDTokenClaimsSessionKey].(oidcencoding.Claims) - if idTokenClaims == nil { - return nil, nil - } - if !ok || idTokenClaims == nil { - return nil, ErrAuthenticatingRequest - } - externalID, ok := idTokenClaims["sub"].(string) - if !ok { - logger.WithField("sub", idTokenClaims["sub"]).Error("Failed type assertion for sub claim") - return nil, ErrAuthenticatingRequest - } - for claimName, expectedValue := range oidcConfig.ValidateIDTokenClaims { - actualValue, ok := idTokenClaims[claimName] - if !ok || actualValue != expectedValue { - logger.WithFields(logging.Fields{ - "claim_name": claimName, - "actual_value": actualValue, - "expected_value": expectedValue, - "missing": !ok, - }).Error("Authentication failed on validating ID token claims") - return nil, ErrAuthenticatingRequest - } - } - friendlyName := "" - if oidcConfig.FriendlyNameClaimName != "" { - friendlyName, _ = idTokenClaims[oidcConfig.FriendlyNameClaimName].(string) - } - user, err := authService.GetUserByExternalID(ctx, externalID) - if err == nil { - return enhanceWithFriendlyName(user, friendlyName), nil - } - if !errors.Is(err, auth.ErrNotFound) { - logger.WithError(err).Error("Failed to get external user from database") - return nil, ErrAuthenticatingRequest - } - u := model.User{ - CreatedAt: time.Now().UTC(), - Source: "oidc", - Username: externalID, - ExternalID: &externalID, - } - _, err = authService.CreateUser(ctx, &u) - if err != nil { - if !errors.Is(err, auth.ErrAlreadyExists) { - logger.WithError(err).Error("Failed to create external user in database") - return nil, ErrAuthenticatingRequest - } - // user already exists - get it: - user, err = authService.GetUserByExternalID(ctx, externalID) - if err != nil { - logger.WithError(err).Error("Failed to get external user from database") - return nil, ErrAuthenticatingRequest - } - return enhanceWithFriendlyName(user, friendlyName), nil - } - initialGroups := oidcConfig.DefaultInitialGroups - if userInitialGroups, ok := idTokenClaims[oidcConfig.InitialGroupsClaimName].(string); ok { - initialGroups = strings.Split(userInitialGroups, ",") - } - for _, g := range initialGroups { - err = authService.AddUserToGroup(ctx, u.Username, strings.TrimSpace(g)) - if err != nil { - logger.WithError(err).Error("Failed to add external user to group") - } - } - return enhanceWithFriendlyName(&u, friendlyName), nil -} - -func userByToken(ctx context.Context, logger logging.Logger, authService auth.Service, tokenString string) (*model.User, error) { - claims, err := auth.VerifyToken(authService.SecretStore().SharedSecret(), tokenString) +func userByToken(ctx context.Context, secretStore crypt.SecretStore, userRepo models.IUserRepo, tokenString string) (*models.User, error) { + claims, err := VerifyToken(secretStore.SharedSecret(), tokenString) // make sure no audience is set for login token if err != nil || !claims.VerifyAudience(LoginAudience, false) { return nil, ErrAuthenticatingRequest } username := claims.Subject - userData, err := authService.GetUser(ctx, username) + userData, err := userRepo.Get(ctx, models.NewGetUserParams().SetName(username)) if err != nil { - logger.WithFields(logging.Fields{ - "token_id": claims.Id, - "username": username, - "subject": claims.Subject, - }).Debug("could not find user id by credentials") + log.With( + "token_id", claims.Id, + "username", username, + "subject", claims.Subject, + ).Debugf("could not find user id by credentials %v", err) return nil, ErrAuthenticatingRequest } return userData, nil } -func userByAuth(ctx context.Context, logger logging.Logger, authenticator auth.Authenticator, authService auth.Service, accessKey string, secretKey string) (*model.User, error) { - // TODO(ariels): Rename keys. +func userByAuth(ctx context.Context, authenticator Authenticator, userRepo models.IUserRepo, accessKey string, secretKey string) (*models.User, error) { username, err := authenticator.AuthenticateUser(ctx, accessKey, secretKey) if err != nil { - logger.WithError(err).WithField("user", accessKey).Error("authenticate") + log.With("user", accessKey).Errorf("authenticate %v", err) return nil, ErrAuthenticatingRequest } - user, err := authService.GetUser(ctx, username) + user, err := userRepo.Get(ctx, models.NewGetUserParams().SetName(username)) if err != nil { - logger.WithError(err).WithFields(logging.Fields{"user_name": username}).Debug("could not find user id by credentials") + log.With("user_name", username).Debugf("could not find user id by credentials %s", err) return nil, ErrAuthenticatingRequest } return user, nil diff --git a/auth/auth_test.go b/auth/auth_test.go index f1b1eb74..89bb1a33 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -19,7 +19,7 @@ func TestLogin_Success(t *testing.T) { // repo mockRepo := models.NewUserRepo(db) // config - mockConfig := &config.Config{Auth: config.AuthConfig{SecretKey: []byte("THIS_MUST_BE_CHANGED_IN_PRODUCTION")}} + mockConfig := &config.AuthConfig{SecretKey: []byte("THIS_MUST_BE_CHANGED_IN_PRODUCTION")} // user userModel := &models.User{} require.NoError(t, gofakeit.Struct(userModel)) diff --git a/auth/authenticator.go b/auth/authenticator.go index 5f7b421f..996865a8 100644 --- a/auth/authenticator.go +++ b/auth/authenticator.go @@ -9,5 +9,5 @@ import "context" type Authenticator interface { // AuthenticateUser authenticates a user matching username and // password and returns their ID. - AuthenticateUser(ctx context.Context, username, password string) (string, error) + AuthenticateUser(ctx context.Context, ak, sk string) (string, error) } diff --git a/auth/basic_auth.go b/auth/basic_auth.go index d0c9eba6..2eafec14 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -24,7 +24,7 @@ type Login struct { Password string `json:"password"` } -func (l *Login) Login(ctx context.Context, repo models.IUserRepo, config *config.Config) (token api.AuthenticationToken, err error) { +func (l *Login) Login(ctx context.Context, repo models.IUserRepo, config *config.AuthConfig) (token api.AuthenticationToken, err error) { // get user encryptedPassword by username ep, err := repo.GetEPByName(ctx, l.Username) if err != nil { @@ -40,7 +40,7 @@ func (l *Login) Login(ctx context.Context, repo models.IUserRepo, config *config // Generate user token loginTime := time.Now() expires := loginTime.Add(expirationDuration) - secretKey := config.Auth.SecretKey + secretKey := config.SecretKey tokenString, err := GenerateJWTLogin(secretKey, l.Username, loginTime, expires) if err != nil { @@ -106,11 +106,11 @@ type UserInfo struct { Token string `json:"token"` } -func (u *UserInfo) UserProfile(ctx context.Context, repo models.IUserRepo, config *config.Config) (api.UserInfo, error) { +func (u *UserInfo) UserProfile(ctx context.Context, repo models.IUserRepo, config *config.AuthConfig) (api.UserInfo, error) { userInfo := api.UserInfo{} // Parse JWT Token token, err := jwt.Parse(u.Token, func(token *jwt.Token) (interface{}, error) { - return config.Auth.SecretKey, nil + return config.SecretKey, nil }) if err != nil { return userInfo, fmt.Errorf("cannot parse token %s %w", token.Raw, err) diff --git a/auth/context.go b/auth/context.go index a2148f31..2a1d829a 100644 --- a/auth/context.go +++ b/auth/context.go @@ -2,8 +2,9 @@ package auth import ( "context" + "fmt" - "github.com/treeverse/lakefs/pkg/auth/model" + "github.com/jiaozifs/jiaozifs/models" ) type contextKey string @@ -12,14 +13,14 @@ const ( userContextKey contextKey = "user" ) -func GetUser(ctx context.Context) (*model.User, error) { - user, ok := ctx.Value(userContextKey).(*model.User) +func GetUser(ctx context.Context) (*models.User, error) { + user, ok := ctx.Value(userContextKey).(*models.User) if !ok { - return nil, ErrUserNotFound + return nil, fmt.Errorf("UserNotFound") } return user, nil } -func WithUser(ctx context.Context, user *model.User) context.Context { +func WithUser(ctx context.Context, user *models.User) context.Context { return context.WithValue(ctx, userContextKey, user) } diff --git a/auth/crypt/encryption_test.go b/auth/crypt/encryption_test.go index de7adbce..f5e3906d 100644 --- a/auth/crypt/encryption_test.go +++ b/auth/crypt/encryption_test.go @@ -5,7 +5,7 @@ import ( "fmt" "testing" - "github.com/treeverse/lakefs/pkg/auth/crypt" + "github.com/jiaozifs/jiaozifs/auth/crypt" ) func TestSecretStore_Encrypt(t *testing.T) { diff --git a/auth/session_store.go b/auth/session_store.go index 8832b06d..3108c62a 100644 --- a/auth/session_store.go +++ b/auth/session_store.go @@ -1 +1,12 @@ package auth + +import ( + "github.com/gorilla/sessions" + "github.com/jiaozifs/jiaozifs/auth/crypt" + "github.com/jiaozifs/jiaozifs/config" +) + +func NewSessionStore(authConfig *config.AuthConfig) sessions.Store { + sstore := crypt.NewSecretStore(authConfig.SecretKey) + return sessions.NewCookieStore(sstore.SharedSecret()) +} diff --git a/auth/token.go b/auth/token.go index 93881e20..05a59f4d 100644 --- a/auth/token.go +++ b/auth/token.go @@ -1,11 +1,16 @@ package auth import ( + "errors" "fmt" "github.com/golang-jwt/jwt" ) +var ( + ErrUnexpectedSigningMethod = errors.New("unexpected signing method") +) + func VerifyToken(secret []byte, tokenString string) (*jwt.StandardClaims, error) { claims := &jwt.StandardClaims{} token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { diff --git a/cmd/daemon.go b/cmd/daemon.go index bcd1b2c4..26b192cb 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -3,6 +3,9 @@ package cmd import ( "context" + "github.com/gorilla/sessions" + "github.com/jiaozifs/jiaozifs/auth" + "github.com/jiaozifs/jiaozifs/block/params" "github.com/jiaozifs/jiaozifs/block" @@ -45,6 +48,7 @@ var daemonCmd = &cobra.Command{ //config fx_opt.Override(new(*config.Config), cfg), fx_opt.Override(new(*config.APIConfig), &cfg.API), + fx_opt.Override(new(*config.AuthConfig), &cfg.Auth), fx_opt.Override(new(*config.DatabaseConfig), &cfg.Database), fx_opt.Override(new(params.AdapterConfig), &cfg.Blockstore), //blockstore @@ -57,6 +61,7 @@ var daemonCmd = &cobra.Command{ fx_opt.Override(fx_opt.NextInvoke(), migrations.MigrateDatabase), //api + fx_opt.Override(new(sessions.Store), auth.NewSessionStore), fx_opt.Override(fx_opt.NextInvoke(), apiImpl.SetupAPI), ) if err != nil { diff --git a/controller/commit_ctl.go b/controller/commit_ctl.go index ac86b65a..c4d778dc 100644 --- a/controller/commit_ctl.go +++ b/controller/commit_ctl.go @@ -21,15 +21,19 @@ type CommitController struct { Repo models.IRepo } -func (commitCtl CommitController) GetEntriesInCommit(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, _ string, _ string, commitHashStr string, params api.GetEntriesInCommitParams) { - commitHash, err := hex.DecodeString(commitHashStr) +func (commitCtl CommitController) GetEntriesInCommit(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, _ string, _ string, params api.GetEntriesInCommitParams) { + refName := "main" + if params.Path != nil { + refName = *params.Ref + } + + ref, err := commitCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(refName)) if err != nil { w.Error(err) return } - commit, err := commitCtl.Repo.ObjectRepo().Get(ctx, &models.GetObjParams{ - Hash: commitHash, - }) + + commit, err := commitCtl.Repo.ObjectRepo().Get(ctx, models.NewGetObjParams().SetHash(ref.CommitHash)) if err != nil { w.Error(err) return @@ -53,13 +57,19 @@ func (commitCtl CommitController) GetEntriesInCommit(ctx context.Context, w *api w.JSON(treeEntry) } -func (commitCtl CommitController) GetCommitDiff(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, _ string, _ string, baseCommitStr string, toCommitStr string, params api.GetCommitDiffParams) { - bashCommitHash, err := hex.DecodeString(baseCommitStr) +func (commitCtl CommitController) GetCommitDiff(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, _ string, _ string, basehead string, params api.GetCommitDiffParams) { + baseHead := strings.Split(basehead, "...") + if len(baseHead) != 2 { + w.BadRequest("invalid basehead must be base...head") + return + } + + bashCommitHash, err := hex.DecodeString(baseHead[0]) if err != nil { w.Error(err) return } - toCommitHash, err := hex.DecodeString(toCommitStr) + toCommitHash, err := hex.DecodeString(baseHead[1]) if err != nil { w.Error(err) return diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index c7823e41..5bf0ce23 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -3,10 +3,19 @@ package controller import ( "context" "errors" + "io" "net/http" "regexp" "time" + openapi_types "github.com/oapi-codegen/runtime/types" + + "github.com/jiaozifs/jiaozifs/utils/hash" + + "github.com/jiaozifs/jiaozifs/versionmgr" + + "github.com/jiaozifs/jiaozifs/auth" + "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/models" "github.com/jiaozifs/jiaozifs/utils" @@ -40,8 +49,8 @@ type RepositoryController struct { Repo models.IRepo } -func (repositoryCtl RepositoryController) ListRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string) { - user, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) +func (repositoryCtl RepositoryController) ListRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request) { + user, err := auth.GetUser(ctx) if err != nil { w.Error(err) return @@ -57,22 +66,22 @@ func (repositoryCtl RepositoryController) ListRepository(ctx context.Context, w w.JSON(repositories) } -func (repositoryCtl RepositoryController) CreateRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, params api.CreateRepositoryParams) { - err := CheckRepositoryName(params.Name) +func (repositoryCtl RepositoryController) CreateRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.CreateRepositoryJSONRequestBody) { + err := CheckRepositoryName(body.Name) if err != nil { w.BadRequest(err.Error()) return } - user, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) + user, err := auth.GetUser(ctx) if err != nil { w.Error(err) return } repository := &models.Repository{ - Name: params.Name, - Description: params.Description, + Name: body.Name, + Description: body.Description, HEAD: "main", CreatorID: user.ID, CreatedAt: time.Now(), @@ -150,3 +159,87 @@ func (repositoryCtl RepositoryController) UpdateRepository(ctx context.Context, return } } + +func (repositoryCtl RepositoryController) GetCommitsInRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repositoryName string, params api.GetCommitsInRepositoryParams) { + user, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) + if err != nil { + w.Error(err) + return + } + + repo, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ + CreatorID: user.ID, + Name: utils.String(repositoryName), + }) + if err != nil { + w.Error(err) + return + } + + refName := repo.HEAD + if params.RefName != nil { + refName = *params.RefName + } + ref, err := repositoryCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(refName)) + if err != nil { + w.Error(err) + return + } + + commit, err := repositoryCtl.Repo.ObjectRepo().Commit(ctx, ref.CommitHash) + if err != nil { + w.Error(err) + return + } + + var commits []api.Commit + iter := versionmgr.NewCommitPreorderIter(versionmgr.NewCommitNode(ctx, commit, repositoryCtl.Repo.ObjectRepo()), nil, nil) + for { + commit, err := iter.Next() + if err == nil { + modelCommit := commit.Commit() + commits = append(commits, api.Commit{ + Author: api.Signature{ + Email: openapi_types.Email(modelCommit.Author.Email), + Name: modelCommit.Author.Name, + When: modelCommit.Author.When, + }, + Committer: api.Signature{ + Email: openapi_types.Email(modelCommit.Committer.Email), + Name: modelCommit.Committer.Name, + When: modelCommit.Committer.When, + }, + CreatedAt: modelCommit.CreatedAt, + Hash: modelCommit.Hash.Hex(), + MergeTag: modelCommit.MergeTag, + Message: modelCommit.Message, + ParentHashes: hash.HexArrayOfHashes(modelCommit.ParentHashes...), + TreeHash: modelCommit.TreeHash.Hex(), + Type: int8(modelCommit.Type), + UpdatedAt: modelCommit.UpdatedAt, + }) + continue + } + if err == io.EOF { + break + } + w.Error(err) + return + } + w.JSON(commits) +} + +func (repositoryCtl RepositoryController) ListRepositories(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string) { + user, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) + if err != nil { + w.Error(err) + return + } + + repos, err := repositoryCtl.Repo.RepositoryRepo().List(ctx, models.NewListRepoParams().SetCreatorID(user.ID)) + if err != nil { + w.Error(err) + return + } + w.JSON(repos) +} diff --git a/controller/user_ctl.go b/controller/user_ctl.go index d28e4e81..ee1ac5fa 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -2,8 +2,13 @@ package controller import ( "context" + "fmt" "net/http" + logging "github.com/ipfs/go-log/v2" + + "github.com/gorilla/sessions" + "github.com/jiaozifs/jiaozifs/config" "github.com/jiaozifs/jiaozifs/models" @@ -12,6 +17,8 @@ import ( "go.uber.org/fx" ) +var userCtlLog = logging.Logger("user_ctl") + const ( AuthHeader = "Authorization" ) @@ -19,11 +26,12 @@ const ( type UserController struct { fx.In - Repo models.IRepo - Config *config.Config + SessionStore sessions.Store + Repo models.IRepo + Config *config.AuthConfig } -func (userCtl UserController) Login(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.LoginJSONRequestBody) { +func (userCtl UserController) Login(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, body api.LoginJSONRequestBody) { login := auth.Login{ Username: body.Username, Password: body.Password, @@ -35,6 +43,15 @@ func (userCtl UserController) Login(ctx context.Context, w *api.JiaozifsResponse w.Error(err) return } + + internalAuthSession, _ := userCtl.SessionStore.Get(r, auth.InternalAuthSessionName) + internalAuthSession.Values[auth.TokenSessionKeyName] = authToken.Token + err = userCtl.SessionStore.Save(r, w, internalAuthSession) + if err != nil { + userCtlLog.Errorf("Failed to save internal auth session %v", err) + w.Code(http.StatusInternalServerError) + return + } w.JSON(authToken) } @@ -56,6 +73,12 @@ func (userCtl UserController) Register(ctx context.Context, w *api.JiaozifsRespo func (userCtl UserController) GetUserInfo(ctx context.Context, w *api.JiaozifsResponse, r *http.Request) { // Get token from Header + user, err := auth.GetUser(ctx) + if err != nil { + w.Error(err) + return + } + fmt.Println(user) tokenString := r.Header.Get(AuthHeader) userInfo := &auth.UserInfo{Token: tokenString} @@ -68,3 +91,18 @@ func (userCtl UserController) GetUserInfo(ctx context.Context, w *api.JiaozifsRe w.JSON(usrInfo) } + +func (userCtl UserController) Logout(_ context.Context, w *api.JiaozifsResponse, r *http.Request) { + session, err := userCtl.SessionStore.Get(r, auth.InternalAuthSessionName) + if err != nil { + w.Error(err) + return + } + session.Options.MaxAge = -1 + if session.Save(r, w) != nil { + userCtlLog.Errorf("Failed to save internal auth session %v", err) + w.Error(err) + return + } + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) +} diff --git a/controller/wip_ctl.go b/controller/wip_ctl.go index 58026bd8..f8b03e50 100644 --- a/controller/wip_ctl.go +++ b/controller/wip_ctl.go @@ -8,10 +8,9 @@ import ( "net/http" "time" + "github.com/jiaozifs/jiaozifs/auth" "github.com/jiaozifs/jiaozifs/versionmgr" - openapi_types "github.com/oapi-codegen/runtime/types" - "github.com/jiaozifs/jiaozifs/utils" "github.com/jiaozifs/jiaozifs/api" @@ -25,8 +24,13 @@ type WipController struct { Repo models.IRepo } -func (wipCtl WipController) CreateWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repository string, refID openapi_types.UUID, params api.CreateWipParams) { - user, err := wipCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) +func (wipCtl WipController) CreateWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, repository string, params api.CreateWipParams) { + user, err := auth.GetUser(ctx) + if err != nil { + w.Error(err) + return + } + ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(params.RefName)) if err != nil { w.Error(err) return @@ -51,7 +55,7 @@ func (wipCtl WipController) CreateWip(ctx context.Context, w *api.JiaozifsRespon CurrentTree: baseCommitID, BaseTree: baseCommitID, RepositoryID: repo.ID, - RefID: refID, + RefID: ref.ID, State: 0, Name: params.Name, CreatorID: user.ID, @@ -66,8 +70,8 @@ func (wipCtl WipController) CreateWip(ctx context.Context, w *api.JiaozifsRespon w.JSON(wip, http.StatusCreated) } -func (wipCtl WipController) GetWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repositoryName string, refID openapi_types.UUID) { - user, err := wipCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) +func (wipCtl WipController) GetWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, repositoryName string, params api.GetWipParams) { + user, err := auth.GetUser(ctx) if err != nil { w.Error(err) return @@ -79,8 +83,14 @@ func (wipCtl WipController) GetWip(ctx context.Context, w *api.JiaozifsResponse, return } + ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(params.RefName)) + if err != nil { + w.Error(err) + return + } + wip, err := wipCtl.Repo.WipRepo().Get(ctx, &models.GetWipParams{ - RefID: refID, + RefID: ref.ID, CreatorID: user.ID, RepositoryID: repository.ID, }) @@ -92,8 +102,8 @@ func (wipCtl WipController) GetWip(ctx context.Context, w *api.JiaozifsResponse, w.JSON(wip) } -func (wipCtl WipController) ListWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repositoryName string) { - user, err := wipCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) +func (wipCtl WipController) ListWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, repositoryName string) { + user, err := auth.GetUser(ctx) if err != nil { w.Error(err) return @@ -114,8 +124,8 @@ func (wipCtl WipController) ListWip(ctx context.Context, w *api.JiaozifsResponse w.JSON(wips) } -func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repositoryName string, refID openapi_types.UUID, params api.CommitWipParams) { - user, err := wipCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) +func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, repositoryName string, params api.CommitWipParams) { + user, err := auth.GetUser(ctx) if err != nil { w.Error(err) return @@ -127,7 +137,7 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon return } - ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetID(refID)) + ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(params.RefName)) if err != nil { w.Error(err) return @@ -139,11 +149,7 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon return } - wip, err := wipCtl.Repo.WipRepo().Get(ctx, &models.GetWipParams{ - RefID: refID, - CreatorID: user.ID, - RepositoryID: repository.ID, - }) + wip, err := wipCtl.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetRefID(ref.ID).SetCreatorID(user.ID).SetRepositoryID(repository.ID)) if err != nil { w.Error(err) return @@ -172,7 +178,7 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon return err } - return repo.RefRepo().UpdateByID(ctx, models.NewUpdateRefParams(refID).SetCommitHash(commit.Commit().Hash)) + return repo.RefRepo().UpdateByID(ctx, models.NewUpdateRefParams(ref.ID).SetCommitHash(commit.Commit().Hash)) }) if err != nil { w.Error(err) @@ -183,8 +189,8 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon } // DeleteWip delete a active working in process -func (wipCtl WipController) DeleteWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repositoryName string, refID openapi_types.UUID) { - user, err := wipCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) +func (wipCtl WipController) DeleteWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, repositoryName string, params api.DeleteWipParams) { + user, err := auth.GetUser(ctx) if err != nil { w.Error(err) return @@ -196,10 +202,16 @@ func (wipCtl WipController) DeleteWip(ctx context.Context, w *api.JiaozifsRespon return } + ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(params.RefName)) + if err != nil { + w.Error(err) + return + } + deleteWipParams := models.NewDeleteWipParams(). SetCreatorID(user.ID). SetRepositoryID(repository.ID). - SetRefID(refID) + SetRefID(ref.ID) err = wipCtl.Repo.WipRepo().Delete(ctx, deleteWipParams) if err != nil { diff --git a/go.mod b/go.mod index 8e8a7d49..92b9d1ef 100644 --- a/go.mod +++ b/go.mod @@ -2,11 +2,14 @@ module github.com/jiaozifs/jiaozifs go 1.20 +replace github.com/flowchartsman/swaggerui => /home/hunjixin/code/swaggerui + require ( cloud.google.com/go/storage v1.33.0 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0 + github.com/MadAppGang/httplog v1.3.0 github.com/aws/aws-sdk-go-v2 v1.23.4 github.com/aws/aws-sdk-go-v2/config v1.25.10 github.com/aws/aws-sdk-go-v2/credentials v1.16.8 @@ -15,10 +18,11 @@ require ( github.com/aws/smithy-go v1.18.1 github.com/benburkert/dns v0.0.0-20190225204957-d356cf78cdfc github.com/brianvoe/gofakeit/v6 v6.25.0 - github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/deepmap/oapi-codegen/v2 v2.0.1-0.20231120160225-add3126ee845 + github.com/emirpasic/gods v1.18.1 github.com/fergusstrange/embedded-postgres v1.25.0 + github.com/flowchartsman/swaggerui v0.0.0-20221017034628-909ed4f3701b github.com/getkin/kin-openapi v0.118.0 github.com/go-chi/chi/v5 v5.0.10 github.com/go-chi/cors v1.2.1 @@ -28,6 +32,7 @@ require ( github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.4.0 + github.com/gorilla/sessions v1.2.2 github.com/hnlq715/golang-lru v0.4.0 github.com/ipfs/go-log/v2 v2.5.1 github.com/matoous/go-nanoid/v2 v2.0.0 @@ -70,6 +75,7 @@ require ( github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect + github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.3 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.8 // indirect @@ -106,6 +112,7 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/gorilla/mux v1.8.0 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v0.3.15 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -123,7 +130,7 @@ require ( github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/minio/md5-simd v1.1.2 // indirect github.com/minio/sha256-simd v1.0.1 // indirect @@ -143,6 +150,7 @@ require ( github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.11.1 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/rs/cors v1.10.1 // indirect github.com/rs/xid v1.5.0 // indirect github.com/sagikazarmark/locafero v0.3.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect diff --git a/go.sum b/go.sum index fb3bc59e..f280904f 100644 --- a/go.sum +++ b/go.sum @@ -61,11 +61,15 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 h1:WpB/QDNLpMw github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/MadAppGang/httplog v1.3.0 h1:1XU54TO8kiqTeO+7oZLKAM3RP/cJ7SadzslRcKspVHo= +github.com/MadAppGang/httplog v1.3.0/go.mod h1:gpYEdkjh/Cda6YxtDy4AB7KY+fR7mb3SqBZw74A5hJ4= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 h1:ZBbLwSJqkHBuFDA6DUhhse0IGJ7T5bemHyNILUjvOq4= +github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2/go.mod h1:VSw57q4QFiWDbRnjdX8Cb3Ow0SFncRw+bA/ofY6Q83w= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/aws/aws-sdk-go-v2 v1.23.4 h1:2P20ZjH0ouSAu/6yZep8oCmTReathLuEu6dwoqEgjts= @@ -121,7 +125,6 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -151,6 +154,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -161,6 +166,8 @@ github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/fergusstrange/embedded-postgres v1.25.0 h1:sa+k2Ycrtz40eCRPOzI7Ry7TtkWXXJ+YRsxpKMDhxK0= github.com/fergusstrange/embedded-postgres v1.25.0/go.mod h1:t/MLs0h9ukYM6FSt99R7InCHs1nW0ordoVCcnzmpTYw= +github.com/flowchartsman/swaggerui v0.0.0-20221017034628-909ed4f3701b h1:oy54yVy300Db264NfQCJubZHpJOl+SoT6udALQdFbSI= +github.com/flowchartsman/swaggerui v0.0.0-20221017034628-909ed4f3701b/go.mod h1:/RJwPD5L4xWgCbqQ1L5cB12ndgfKKT54n9cZFf+8pus= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= @@ -168,7 +175,6 @@ github.com/getkin/kin-openapi v0.118.0 h1:z43njxPmJ7TaPpMSCQb7PN0dEYno4tyBPQcrFd github.com/getkin/kin-openapi v0.118.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= @@ -241,6 +247,7 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -273,12 +280,17 @@ github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qK github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= +github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hnlq715/golang-lru v0.4.0 h1:gyo/wIvLE6Upf1wucAfwTjpR+BQ5Lli2766H2MnNPv0= github.com/hnlq715/golang-lru v0.4.0/go.mod h1:RBkgDAtlu0SgTPvpb4VW2/RQnkCBMRD3Lr6B9RhsAS8= +github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= @@ -336,8 +348,8 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= @@ -404,6 +416,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= +github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/models/merge_request.go b/models/merge_request.go index 7cb020c3..487a7082 100644 --- a/models/merge_request.go +++ b/models/merge_request.go @@ -21,13 +21,14 @@ type MergeRequest struct { MergeStatus MergeStatus `bun:"merge_status,notnull"` Description *string `bun:"description"` - AuthorID uuid.UUID `bun:"author_id,type:bytea,notnull"` - AssigneeID uuid.UUID `bun:"assignee_id,type:bytea"` - MergeUserID uuid.UUID `bun:"merge_user_id,type:bytea"` + AuthorID uuid.UUID `bun:"author_id,type:bytea,notnull"` + AssigneeID uuid.UUID `bun:"assignee_id,type:bytea"` + MergeUserID uuid.UUID `bun:"merge_user_id,type:bytea"` ApprovalsBeforeMerge int `bun:"approvals_before_merge"` - CreatedAt time.Time `bun:"created_at"` - UpdatedAt time.Time `bun:"updated_at"` + + CreatedAt time.Time `bun:"created_at"` + UpdatedAt time.Time `bun:"updated_at"` } type GetMergeRequestParams struct { diff --git a/models/repository.go b/models/repository.go index df3412dc..0180a239 100644 --- a/models/repository.go +++ b/models/repository.go @@ -50,7 +50,7 @@ type ListRepoParams struct { CreatorID uuid.UUID } -func NewListRepoParam() *ListRepoParams { +func NewListRepoParams() *ListRepoParams { return &ListRepoParams{} } diff --git a/models/repository_test.go b/models/repository_test.go index 3d60dc9f..d958b3e5 100644 --- a/models/repository_test.go +++ b/models/repository_test.go @@ -44,7 +44,7 @@ func TestRepositoryRepo_Insert(t *testing.T) { require.NotEqual(t, uuid.Nil, secRepo.ID) //list - repos, err := repo.List(ctx, models.NewListRepoParam().SetCreatorID(secModel.CreatorID)) + repos, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID)) require.NoError(t, err) require.Len(t, repos, 2) diff --git a/utils/hash/hash.go b/utils/hash/hash.go index dee94781..256e3ead 100644 --- a/utils/hash/hash.go +++ b/utils/hash/hash.go @@ -51,3 +51,23 @@ func (hash Hash) MarshalJSON() ([]byte, error) { } return []byte(`"` + hash.Hex() + `"`), nil } + +func HashesOfHexArray(hashesStr ...string) ([]Hash, error) { + hashes := make([]Hash, len(hashesStr)) + for i, hashStr := range hashesStr { + hash, err := hex.DecodeString(hashStr) + if err != nil { + return nil, err + } + hashes[i] = hash + } + return hashes, nil +} + +func HexArrayOfHashes(hashes ...Hash) []string { + hashesStr := make([]string, len(hashes)) + for i, hash := range hashes { + hashesStr[i] = hash.Hex() + } + return hashesStr +} diff --git a/utils/hash/hash_test.go b/utils/hash/hash_test.go index 496de5d8..120ca540 100644 --- a/utils/hash/hash_test.go +++ b/utils/hash/hash_test.go @@ -33,3 +33,14 @@ func TestHashJSON(t *testing.T) { require.Equal(t, "", string(a.H)) }) } + +func TestHexArrayOfHashes(t *testing.T) { + hashes := []Hash{Hash("aaaaaa"), Hash("bbbbbb"), Hash("cccccc"), Hash("dddddd")} + hexArray := HexArrayOfHashes(hashes...) + require.Equal(t, "616161616161", hexArray[0]) + require.Equal(t, "636363636363", hexArray[2]) + + hashes2, err := HashesOfHexArray(hexArray...) + require.NoError(t, err) + require.Equal(t, hashes, hashes2) +} diff --git a/versionmgr/commit_walker_ctime.go b/versionmgr/commit_walker_ctime.go index fbddf1d2..db142ee1 100644 --- a/versionmgr/commit_walker_ctime.go +++ b/versionmgr/commit_walker_ctime.go @@ -1,17 +1,14 @@ -package object +package versionmgr import ( "io" "github.com/emirpasic/gods/trees/binaryheap" - - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/storer" ) type commitIteratorByCTime struct { - seenExternal map[plumbing.Hash]bool - seen map[plumbing.Hash]bool + seenExternal map[string]bool + seen map[string]bool heap *binaryheap.Heap } @@ -24,17 +21,17 @@ type commitIteratorByCTime struct { // cannot be traversed (e.g. missing objects). Ignore allows to skip some // commits from being iterated. func NewCommitIterCTime( - c *Commit, - seenExternal map[plumbing.Hash]bool, - ignore []plumbing.Hash, + c *CommitNode, + seenExternal map[string]bool, + ignore []string, ) CommitIter { - seen := make(map[plumbing.Hash]bool) + seen := make(map[string]bool) for _, h := range ignore { seen[h] = true } heap := binaryheap.NewWith(func(a, b interface{}) int { - if a.(*Commit).Committer.When.Before(b.(*Commit).Committer.When) { + if a.(*CommitNode).Commit().Committer.When.Before(b.(*CommitNode).Commit().Committer.When) { return 1 } return -1 @@ -48,26 +45,26 @@ func NewCommitIterCTime( } } -func (w *commitIteratorByCTime) Next() (*Commit, error) { - var c *Commit +func (w *commitIteratorByCTime) Next() (*CommitNode, error) { + var c *CommitNode for { cIn, ok := w.heap.Pop() if !ok { return nil, io.EOF } - c = cIn.(*Commit) + c = cIn.(*CommitNode) - if w.seen[c.Hash] || w.seenExternal[c.Hash] { + if w.seen[c.Commit().Hash.Hex()] || w.seenExternal[c.Commit().Hash.Hex()] { continue } - w.seen[c.Hash] = true + w.seen[c.Commit().Hash.Hex()] = true - for _, h := range c.ParentHashes { - if w.seen[h] || w.seenExternal[h] { + for _, h := range c.Commit().ParentHashes { + if w.seen[h.Hex()] || w.seenExternal[h.Hex()] { continue } - pc, err := GetCommit(c.s, h) + pc, err := c.GetCommit(h) if err != nil { return nil, err } @@ -78,7 +75,7 @@ func (w *commitIteratorByCTime) Next() (*Commit, error) { } } -func (w *commitIteratorByCTime) ForEach(cb func(*Commit) error) error { +func (w *commitIteratorByCTime) ForEach(cb func(*CommitNode) error) error { for { c, err := w.Next() if err == io.EOF { @@ -89,7 +86,7 @@ func (w *commitIteratorByCTime) ForEach(cb func(*Commit) error) error { } err = cb(c) - if err == storer.ErrStop { + if err == ErrStop { break } if err != nil { From 6a935eb0bff85b70eca0eb823385c7136e08ce7a Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Wed, 13 Dec 2023 22:10:02 +0800 Subject: [PATCH 070/210] feat: add api to fetch change in wip --- api/jiaozifs.gen.go | 314 +++++++++++++++++++++++++++++++-------- api/swagger.yml | 47 +++++- controller/commit_ctl.go | 1 + controller/wip_ctl.go | 96 +++++++++++- go.mod | 5 +- go.sum | 1 - models/wip.go | 8 +- models/wip_test.go | 4 +- versionmgr/commit.go | 27 +--- versionmgr/worktree.go | 21 +++ 10 files changed, 414 insertions(+), 110 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index ad346a6f..647e31cc 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -144,7 +144,7 @@ type VersionResult struct { // Wip defines model for Wip. type Wip struct { - BaseTree *string `json:"BaseTree,omitempty"` + BaseCommit *string `json:"BaseCommit,omitempty"` CreatedAt *time.Time `json:"CreatedAt,omitempty"` CreatorID *openapi_types.UUID `json:"CreatorID,omitempty"` CurrentTree *string `json:"CurrentTree,omitempty"` @@ -257,11 +257,17 @@ type CreateWipParams struct { // Name wip name Name string `form:"name" json:"name"` - // BaseCommitID base commit to create wip - BaseCommitID string `form:"baseCommitID" json:"baseCommitID"` + // RefName ref name + RefName string `form:"refName" json:"refName"` +} +// GetWipChangesParams defines parameters for GetWipChanges. +type GetWipChangesParams struct { // RefName ref name RefName string `form:"refName" json:"refName"` + + // Path path + Path *string `form:"path,omitempty" json:"path,omitempty"` } // CommitWipParams defines parameters for CommitWip. @@ -429,6 +435,9 @@ type ClientInterface interface { // CreateWip request CreateWip(ctx context.Context, repository string, params *CreateWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetWipChanges request + GetWipChanges(ctx context.Context, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // CommitWip request CommitWip(ctx context.Context, repository string, params *CommitWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -724,6 +733,18 @@ func (c *Client) CreateWip(ctx context.Context, repository string, params *Creat return c.Client.Do(req) } +func (c *Client) GetWipChanges(ctx context.Context, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetWipChangesRequest(c.Server, repository, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) CommitWip(ctx context.Context, repository string, params *CommitWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewCommitWipRequest(c.Server, repository, params) if err != nil { @@ -1824,7 +1845,7 @@ func NewCreateWipRequest(server string, repository string, params *CreateWipPara } } - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "baseCommitID", runtime.ParamLocationQuery, params.BaseCommitID); err != nil { + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -1836,6 +1857,46 @@ func NewCreateWipRequest(server string, repository string, params *CreateWipPara } } + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("POST", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetWipChangesRequest generates requests for GetWipChanges +func NewGetWipChangesRequest(server string, repository string, params *GetWipChangesParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/wip/%s/changes", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { @@ -1848,10 +1909,26 @@ func NewCreateWipRequest(server string, repository string, params *CreateWipPara } } + if params.Path != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + queryURL.RawQuery = queryValues.Encode() } - req, err := http.NewRequest("POST", queryURL.String(), nil) + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } @@ -2072,6 +2149,9 @@ type ClientWithResponsesInterface interface { // CreateWipWithResponse request CreateWipWithResponse(ctx context.Context, repository string, params *CreateWipParams, reqEditors ...RequestEditorFn) (*CreateWipResponse, error) + // GetWipChangesWithResponse request + GetWipChangesWithResponse(ctx context.Context, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*GetWipChangesResponse, error) + // CommitWipWithResponse request CommitWipWithResponse(ctx context.Context, repository string, params *CommitWipParams, reqEditors ...RequestEditorFn) (*CommitWipResponse, error) @@ -2511,6 +2591,28 @@ func (r CreateWipResponse) StatusCode() int { return 0 } +type GetWipChangesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Change +} + +// Status returns HTTPResponse.Status +func (r GetWipChangesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetWipChangesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type CommitWipResponse struct { Body []byte HTTPResponse *http.Response @@ -2767,6 +2869,15 @@ func (c *ClientWithResponses) CreateWipWithResponse(ctx context.Context, reposit return ParseCreateWipResponse(rsp) } +// GetWipChangesWithResponse request returning *GetWipChangesResponse +func (c *ClientWithResponses) GetWipChangesWithResponse(ctx context.Context, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*GetWipChangesResponse, error) { + rsp, err := c.GetWipChanges(ctx, repository, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetWipChangesResponse(rsp) +} + // CommitWipWithResponse request returning *CommitWipResponse func (c *ClientWithResponses) CommitWipWithResponse(ctx context.Context, repository string, params *CommitWipParams, reqEditors ...RequestEditorFn) (*CommitWipResponse, error) { rsp, err := c.CommitWip(ctx, repository, params, reqEditors...) @@ -3225,6 +3336,32 @@ func ParseCreateWipResponse(rsp *http.Response) (*CreateWipResponse, error) { return response, nil } +// ParseGetWipChangesResponse parses an HTTP response from a GetWipChangesWithResponse call +func ParseGetWipChangesResponse(rsp *http.Response) (*GetWipChangesResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetWipChangesResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Change + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseCommitWipResponse parses an HTTP response from a CommitWipWithResponse call func ParseCommitWipResponse(rsp *http.Response) (*CommitWipResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -3339,6 +3476,9 @@ type ServerInterface interface { // create working in process // (POST /wip/{repository}) CreateWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string, params CreateWipParams) + // get working in process changes + // (GET /wip/{repository}/changes) + GetWipChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string, params GetWipChangesParams) // commit working in process to branch // (POST /wip/{repository}/commit) CommitWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string, params CommitWipParams) @@ -3470,6 +3610,12 @@ func (_ Unimplemented) CreateWip(ctx context.Context, w *JiaozifsResponse, r *ht w.WriteHeader(http.StatusNotImplemented) } +// get working in process changes +// (GET /wip/{repository}/changes) +func (_ Unimplemented) GetWipChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string, params GetWipChangesParams) { + w.WriteHeader(http.StatusNotImplemented) +} + // commit working in process to branch // (POST /wip/{repository}/commit) func (_ Unimplemented) CommitWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string, params CommitWipParams) { @@ -4496,21 +4642,56 @@ func (siw *ServerInterfaceWrapper) CreateWip(w http.ResponseWriter, r *http.Requ return } - // ------------- Required query parameter "baseCommitID" ------------- + // ------------- Required query parameter "refName" ------------- - if paramValue := r.URL.Query().Get("baseCommitID"); paramValue != "" { + if paramValue := r.URL.Query().Get("refName"); paramValue != "" { } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "baseCommitID"}) + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) return } - err = runtime.BindQueryParameter("form", true, true, "baseCommitID", r.URL.Query(), ¶ms.BaseCommitID) + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CreateWip(r.Context(), &JiaozifsResponse{w}, r, repository, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// GetWipChanges operation middleware +func (siw *ServerInterfaceWrapper) GetWipChanges(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "baseCommitID", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) return } + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetWipChangesParams + // ------------- Required query parameter "refName" ------------- if paramValue := r.URL.Query().Get("refName"); paramValue != "" { @@ -4526,8 +4707,16 @@ func (siw *ServerInterfaceWrapper) CreateWip(w http.ResponseWriter, r *http.Requ return } + // ------------- Optional query parameter "path" ------------- + + err = runtime.BindQueryParameter("form", true, false, "path", r.URL.Query(), ¶ms.Path) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CreateWip(r.Context(), &JiaozifsResponse{w}, r, repository, params) + siw.Handler.GetWipChanges(r.Context(), &JiaozifsResponse{w}, r, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -4805,6 +4994,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/wip/{repository}", wrapper.CreateWip) }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/wip/{repository}/changes", wrapper.GetWipChanges) + }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/wip/{repository}/commit", wrapper.CommitWip) }) @@ -4818,56 +5010,56 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "AA==", + "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", + "dW+38sB869XdfwMAAP//t0YWRJNHAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 19232c35..404fba9a 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -190,7 +190,7 @@ components: type: string CurrentTree: type: string - BaseTree: + BaseCommit: type: string RepositoryID: type: string @@ -693,12 +693,6 @@ paths: required: true schema: type: string - - in: query - name: baseCommitID - description: base commit to create wip - required: true - schema: - type: string responses: 201: description: working in process created @@ -715,6 +709,45 @@ paths: 502: description: internal server error + /wip/{repository}/changes: + parameters: + - in: path + name: repository + required: true + schema: + type: string + get: + tags: + - wip + operationId: getWipChanges + summary: get working in process changes + parameters: + - in: query + name: refName + description: ref name + required: true + schema: + type: string + - in: query + name: path + description: path + required: false + schema: + type: string + responses: + 200: + description: working in process changes + content: + application/json: + schema: + $ref: "#/components/schemas/Change" + 400: + description: ValidationError + 401: + description: Unauthorized + 403: + description: Forbidden + /wip/{repository}/commit: parameters: - in: path diff --git a/controller/commit_ctl.go b/controller/commit_ctl.go index c4d778dc..624d9dd7 100644 --- a/controller/commit_ctl.go +++ b/controller/commit_ctl.go @@ -111,6 +111,7 @@ func (commitCtl CommitController) GetCommitDiff(ctx context.Context, w *api.Jiao if change.To() != nil { apiChange.ToHash = utils.String(hex.EncodeToString(change.To().Hash())) } + changesResp = append(changesResp, apiChange) } return nil }) diff --git a/controller/wip_ctl.go b/controller/wip_ctl.go index f8b03e50..83e6c23f 100644 --- a/controller/wip_ctl.go +++ b/controller/wip_ctl.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "fmt" "net/http" + "strings" "time" "github.com/jiaozifs/jiaozifs/auth" @@ -45,15 +46,15 @@ func (wipCtl WipController) CreateWip(ctx context.Context, w *api.JiaozifsRespon return } - baseCommitID, err := hex.DecodeString(params.BaseCommitID) + baseCommit, err := wipCtl.Repo.ObjectRepo().Commit(ctx, ref.CommitHash) if err != nil { w.Error(err) return } wip := &models.WorkingInProcess{ - CurrentTree: baseCommitID, - BaseTree: baseCommitID, + CurrentTree: baseCommit.TreeHash, + BaseCommit: ref.CommitHash, RepositoryID: repo.ID, RefID: ref.ID, State: 0, @@ -155,7 +156,7 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon return } - if !bytes.Equal(commit.TreeHash, wip.BaseTree) { + if !bytes.Equal(commit.Hash, wip.BaseCommit) { w.Error(fmt.Errorf("base commit not equal with branch, please update wip")) return } @@ -172,8 +173,8 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon return err } - wip.BaseTree = commit.Commit().TreeHash //set for response - err = repo.WipRepo().UpdateByID(ctx, models.NewUpdateWipParams(wip.ID).SetBaseTree(commit.Commit().TreeHash)) + wip.BaseCommit = commit.Commit().Hash //set for response + err = repo.WipRepo().UpdateByID(ctx, models.NewUpdateWipParams(wip.ID).SetBaseCommit(wip.BaseCommit)) if err != nil { return err } @@ -221,3 +222,86 @@ func (wipCtl WipController) DeleteWip(ctx context.Context, w *api.JiaozifsRespon w.OK() } + +func (wipCtl WipController) GetWipChanges(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, repositoryName string, params api.GetWipChangesParams) { + user, err := auth.GetUser(ctx) + if err != nil { + w.Error(err) + return + } + + repository, err := wipCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName)) + if err != nil { + w.Error(err) + return + } + + ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(params.RefName)) + if err != nil { + w.Error(err) + return + } + + wip, err := wipCtl.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetCreatorID(user.ID).SetRepositoryID(repository.ID).SetRefID(ref.ID)) + if err != nil { + w.Error(err) + return + } + + commit, err := wipCtl.Repo.ObjectRepo().Commit(ctx, wip.BaseCommit) + if err != nil { + w.Error(err) + return + } + + workTree, err := versionmgr.NewWorkTree(ctx, wipCtl.Repo.ObjectRepo(), models.NewRootTreeEntry(commit.TreeHash)) + if err != nil { + w.Error(err) + return + } + + if bytes.Equal(commit.TreeHash, wip.CurrentTree) { + w.JSON([]api.Change{}) //no change return nothing + return + } + + changes, err := workTree.Diff(ctx, wip.CurrentTree) + if err != nil { + w.Error(err) + return + } + + var path string + if params.Path != nil { + path = *params.Path + } + + var changesResp []api.Change + err = changes.ForEach(func(change versionmgr.IChange) error { + action, err := change.Action() + if err != nil { + return err + } + fullPath := change.Path() + if strings.HasPrefix(fullPath, path) { + apiChange := api.Change{ + Action: int(action), + Path: fullPath, + } + if change.From() != nil { + apiChange.BaseHash = utils.String(hex.EncodeToString(change.From().Hash())) + } + if change.To() != nil { + apiChange.ToHash = utils.String(hex.EncodeToString(change.To().Hash())) + } + changesResp = append(changesResp, apiChange) + } + return nil + }) + if err != nil { + w.Error(err) + return + } + + w.JSON(changes) +} diff --git a/go.mod b/go.mod index 92b9d1ef..4e6cd264 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,6 @@ module github.com/jiaozifs/jiaozifs go 1.20 -replace github.com/flowchartsman/swaggerui => /home/hunjixin/code/swaggerui - require ( cloud.google.com/go/storage v1.33.0 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0 @@ -25,7 +23,6 @@ require ( github.com/flowchartsman/swaggerui v0.0.0-20221017034628-909ed4f3701b github.com/getkin/kin-openapi v0.118.0 github.com/go-chi/chi/v5 v5.0.10 - github.com/go-chi/cors v1.2.1 github.com/go-git/go-git/v5 v5.10.1 github.com/go-openapi/swag v0.22.4 github.com/go-test/deep v1.1.0 @@ -46,6 +43,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.17.0 github.com/puzpuzpuz/xsync v1.5.2 + github.com/rs/cors v1.10.1 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.17.0 github.com/stretchr/testify v1.8.4 @@ -150,7 +148,6 @@ require ( github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.11.1 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect - github.com/rs/cors v1.10.1 // indirect github.com/rs/xid v1.5.0 // indirect github.com/sagikazarmark/locafero v0.3.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect diff --git a/go.sum b/go.sum index f280904f..b432205d 100644 --- a/go.sum +++ b/go.sum @@ -175,7 +175,6 @@ github.com/getkin/kin-openapi v0.118.0 h1:z43njxPmJ7TaPpMSCQb7PN0dEYno4tyBPQcrFd github.com/getkin/kin-openapi v0.118.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git/v5 v5.10.1 h1:tu8/D8i+TWxgKpzQ3Vc43e+kkhXqtsZCKI/egajKnxk= diff --git a/models/wip.go b/models/wip.go index f1eb51c6..9bf19d38 100644 --- a/models/wip.go +++ b/models/wip.go @@ -21,7 +21,7 @@ type WorkingInProcess struct { ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` Name string `bun:"name,notnull"` CurrentTree hash.Hash `bun:"current_tree,type:bytea,notnull"` - BaseTree hash.Hash `bun:"base_tree,type:bytea,notnull"` + BaseCommit hash.Hash `bun:"base_commit,type:bytea,notnull"` RepositoryID uuid.UUID `bun:"repository_id,type:uuid,notnull"` RefID uuid.UUID `bun:"ref_id,type:uuid,notnull"` State WipState `bun:"state,notnull"` @@ -120,7 +120,7 @@ type UpdateWipParams struct { bun.BaseModel `bun:"table:wips"` ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` CurrentTree hash.Hash `bun:"current_tree,type:bytea,notnull"` - BaseTree hash.Hash `bun:"base_tree,type:bytea,notnull"` + BaseCommit hash.Hash `bun:"base_commit,type:bytea,notnull"` State WipState `bun:"state,notnull"` UpdatedAt time.Time `bun:"updated_at"` } @@ -134,8 +134,8 @@ func (up *UpdateWipParams) SetCurrentTree(currentTree hash.Hash) *UpdateWipParam return up } -func (up *UpdateWipParams) SetBaseTree(baseTree hash.Hash) *UpdateWipParams { - up.BaseTree = baseTree +func (up *UpdateWipParams) SetBaseCommit(commitHash hash.Hash) *UpdateWipParams { + up.BaseCommit = commitHash return up } diff --git a/models/wip_test.go b/models/wip_test.go index ac4ec3ce..a9fbe79e 100644 --- a/models/wip_test.go +++ b/models/wip_test.go @@ -92,7 +92,7 @@ func TestWipRepoUpdateByID(t *testing.T) { updateModel := models.NewUpdateWipParams(newWipModel.ID). SetState(models.Completed). - SetBaseTree(hash.Hash("mock base hash")). + SetBaseCommit(hash.Hash("mock base hash")). SetCurrentTree(hash.Hash("mock hash")) err = repo.UpdateByID(ctx, updateModel) @@ -100,6 +100,6 @@ func TestWipRepoUpdateByID(t *testing.T) { updatedUser, err := repo.Get(ctx, models.NewGetWipParams().SetID(newWipModel.ID)) require.NoError(t, err) require.Equal(t, models.Completed, updatedUser.State) - require.Equal(t, "mock base hash", string(updatedUser.BaseTree)) + require.Equal(t, "mock base hash", string(updatedUser.BaseCommit)) require.Equal(t, "mock hash", string(updatedUser.CurrentTree)) } diff --git a/versionmgr/commit.go b/versionmgr/commit.go index 00336412..f318c81f 100644 --- a/versionmgr/commit.go +++ b/versionmgr/commit.go @@ -1,17 +1,13 @@ package versionmgr import ( - "bytes" "context" "fmt" "time" - "github.com/go-git/go-git/v5/utils/merkletrie" - "github.com/go-git/go-git/v5/utils/merkletrie/noder" "github.com/google/uuid" logging "github.com/ipfs/go-log/v2" "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/models/filemode" "github.com/jiaozifs/jiaozifs/utils/hash" ) @@ -100,35 +96,16 @@ func (commitOp *CommitOp) AddCommit(ctx context.Context, committer *models.User, // DiffCommit find file changes in two commit func (commitOp *CommitOp) DiffCommit(ctx context.Context, toCommitID hash.Hash) (*Changes, error) { - fromNode, err := NewTreeNode(ctx, models.TreeEntry{ - Name: "", - Mode: filemode.Dir, - Hash: commitOp.commit.TreeHash, - }, commitOp.object) + workTree, err := NewWorkTree(ctx, commitOp.object, models.NewRootTreeEntry(commitOp.Commit().TreeHash)) if err != nil { return nil, err } - toCommit, err := commitOp.object.Commit(ctx, toCommitID) if err != nil { return nil, err } - toNode, err := NewTreeNode(ctx, models.TreeEntry{ - Name: "", - Mode: filemode.Dir, - Hash: toCommit.TreeHash, - }, commitOp.object) - if err != nil { - return nil, err - } - changes, err := merkletrie.DiffTreeContext(ctx, fromNode, toNode, func(a, b noder.Hasher) bool { - return bytes.Equal(a.Hash(), b.Hash()) - }) - if err != nil { - return nil, err - } - return newChanges(changes), nil + return workTree.Diff(ctx, toCommit.TreeHash) } // Merge implement merge like git, docs https://en.wikipedia.org/wiki/Merge_(version_control) diff --git a/versionmgr/worktree.go b/versionmgr/worktree.go index 9caa5b9c..8c54fc2b 100644 --- a/versionmgr/worktree.go +++ b/versionmgr/worktree.go @@ -1,6 +1,7 @@ package versionmgr import ( + "bytes" "context" "errors" "fmt" @@ -10,6 +11,8 @@ import ( "strings" "time" + "github.com/go-git/go-git/v5/utils/merkletrie/noder" + "github.com/jiaozifs/jiaozifs/utils/httputil" "github.com/go-git/go-git/v5/utils/merkletrie" @@ -564,3 +567,21 @@ func (workTree *WorkTree) ApplyOneChange(ctx context.Context, change IChange) er } return fmt.Errorf("unexpect change action: %s", action) } + +func (workTree *WorkTree) Diff(ctx context.Context, rootTreeHash hash.Hash) (*Changes, error) { + toNode, err := NewTreeNode(ctx, models.NewRootTreeEntry(rootTreeHash), workTree.object) + if err != nil { + return nil, err + } + + changes, err := merkletrie.DiffTreeContext(ctx, workTree.Root(), toNode, func(a, b noder.Hasher) bool { + return bytes.Equal(a.Hash(), b.Hash()) + }) + if err != nil { + return nil, err + } + if err != nil { + return nil, err + } + return newChanges(changes), nil +} From b99157f39871bda9e38ec57242333581e89b0ff2 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Wed, 13 Dec 2023 22:38:34 +0800 Subject: [PATCH 071/210] fix: fix invalid http response order --- api/custom_response.go | 7 +++++-- api/custom_response_test.go | 8 ++++---- cmd/version.go | 1 + 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/api/custom_response.go b/api/custom_response.go index 2b85a0b6..4eb5986b 100644 --- a/api/custom_response.go +++ b/api/custom_response.go @@ -16,12 +16,14 @@ type JiaozifsResponse struct { // if not specific code, default code is 200. given code will // overwrite default code, if more than one code, the first one will be used. func (response *JiaozifsResponse) JSON(v any, code ...int) { + response.Header().Set("Content-Type", "application/json") + if len(code) == 0 { response.WriteHeader(http.StatusOK) } else { response.WriteHeader(code[0]) } - response.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(response.ResponseWriter).Encode(v) if err != nil { response.Error(err) @@ -64,12 +66,13 @@ func (response *JiaozifsResponse) Error(err error) { // if not specific code, default code is 200. given code will // overwrite default code, if more than one code, the first one will be used. func (response *JiaozifsResponse) String(msg string, code ...int) { + response.Header().Set("Content-Type", "text/plain;charset=UTF-8") + if len(code) == 0 { response.WriteHeader(http.StatusOK) } else { response.WriteHeader(code[0]) } - response.Header().Set("Content-Type", "text/plain;charset=UTF-8") _, _ = response.Write([]byte(msg)) } diff --git a/api/custom_response_test.go b/api/custom_response_test.go index e5931a49..04f4a07a 100644 --- a/api/custom_response_test.go +++ b/api/custom_response_test.go @@ -80,10 +80,10 @@ func TestJiaozifsResponse(t *testing.T) { resp := NewMockResponseWriter(ctrl) jzResp := JiaozifsResponse{resp} - resp.EXPECT().WriteHeader(http.StatusOK) resp.EXPECT().Header().DoAndReturn(func() http.Header { return make(http.Header) }) + resp.EXPECT().WriteHeader(http.StatusOK) resp.EXPECT().Write([]byte("test")) jzResp.String("test") }) @@ -93,10 +93,10 @@ func TestJiaozifsResponse(t *testing.T) { resp := NewMockResponseWriter(ctrl) jzResp := JiaozifsResponse{resp} - resp.EXPECT().WriteHeader(http.StatusCreated) resp.EXPECT().Header().DoAndReturn(func() http.Header { return make(http.Header) }) + resp.EXPECT().WriteHeader(http.StatusCreated) resp.EXPECT().Write([]byte("test")) jzResp.String("test", http.StatusCreated) }) @@ -106,12 +106,12 @@ func TestJiaozifsResponse(t *testing.T) { resp := NewMockResponseWriter(ctrl) jzResp := JiaozifsResponse{resp} - resp.EXPECT().WriteHeader(http.StatusOK) resp.EXPECT().Header().DoAndReturn(func() http.Header { return make(http.Header) }) - + resp.EXPECT().WriteHeader(http.StatusOK) resp.EXPECT().Write([]byte("{\"Name\":\"aa\"}\n")) + jzResp.JSON(struct { Name string }{Name: "aa"}) diff --git a/cmd/version.go b/cmd/version.go index 5a36093d..20d51ced 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -40,6 +40,7 @@ var versionCmd = &cobra.Command{ if okResp.JSON200 == nil { return fmt.Errorf("request version fail %d %s", okResp.HTTPResponse.StatusCode, okResp.HTTPResponse.Body) } + fmt.Println("Runtime Version ", okResp.JSON200.Version) fmt.Println("Runtime API Version ", okResp.JSON200.ApiVersion) return nil From 22e23774dc00cc106bb29c260642c853224a84a5 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Wed, 13 Dec 2023 22:41:44 +0800 Subject: [PATCH 072/210] chore: make lint happy --- .fend.yaml | 1 + api/tmpls/chi/chi-middleware.tmpl | 2 +- controller/wip_ctl.go | 2 +- versionmgr/changes.go | 3 +-- versionmgr/commit_test.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.fend.yaml b/.fend.yaml index 53318ff1..cf67ebc3 100644 --- a/.fend.yaml +++ b/.fend.yaml @@ -7,3 +7,4 @@ skip: extension: - .input - .output + - .jpg diff --git a/api/tmpls/chi/chi-middleware.tmpl b/api/tmpls/chi/chi-middleware.tmpl index 833717f0..cb649a0d 100644 --- a/api/tmpls/chi/chi-middleware.tmpl +++ b/api/tmpls/chi/chi-middleware.tmpl @@ -275,4 +275,4 @@ func (e *TooManyValuesForParamError) Error() string { // RawSpec hack to get swagger json for swagger ui func RawSpec() ([]byte, error) { return rawSpec() -} \ No newline at end of file +} diff --git a/controller/wip_ctl.go b/controller/wip_ctl.go index 83e6c23f..8b4d0a00 100644 --- a/controller/wip_ctl.go +++ b/controller/wip_ctl.go @@ -223,7 +223,7 @@ func (wipCtl WipController) DeleteWip(ctx context.Context, w *api.JiaozifsRespon w.OK() } -func (wipCtl WipController) GetWipChanges(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, repositoryName string, params api.GetWipChangesParams) { +func (wipCtl WipController) GetWipChanges(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, repositoryName string, params api.GetWipChangesParams) { user, err := auth.GetUser(ctx) if err != nil { w.Error(err) diff --git a/versionmgr/changes.go b/versionmgr/changes.go index 4a2e74f7..35d526b5 100644 --- a/versionmgr/changes.go +++ b/versionmgr/changes.go @@ -8,9 +8,8 @@ import ( "sort" "strings" - "github.com/go-git/go-git/v5/utils/merkletrie/noder" - "github.com/go-git/go-git/v5/utils/merkletrie" + "github.com/go-git/go-git/v5/utils/merkletrie/noder" ) var ( diff --git a/versionmgr/commit_test.go b/versionmgr/commit_test.go index 86bd2bb4..c81530b2 100644 --- a/versionmgr/commit_test.go +++ b/versionmgr/commit_test.go @@ -370,7 +370,7 @@ func makeRef(ctx context.Context, refRepo models.IRefRepo, name string, repoID u func makeWip(ctx context.Context, wipRepo models.IWipRepo, repoID, refID uuid.UUID, parentHash, curHash hash.Hash) (*models.WorkingInProcess, error) { wip := &models.WorkingInProcess{ CurrentTree: curHash, - BaseTree: parentHash, + BaseCommit: parentHash, RefID: refID, RepositoryID: repoID, CreatorID: uuid.UUID{}, From 5d336799d4740912cbe1027a9b4aaa8a924e0b88 Mon Sep 17 00:00:00 2001 From: zjy Date: Thu, 14 Dec 2023 10:42:14 +0800 Subject: [PATCH 073/210] feat: branch interface manage --- api/api_impl/impl.go | 1 + api/jiaozifs.gen.go | 1308 ++++++++++++++++++++++++++++++-------- api/resp.gen.go | 2 +- api/swagger.yml | 145 ++++- auth/errors.go | 2 - controller/branch_ctl.go | 158 +++++ models/ref.go | 43 ++ 7 files changed, 1389 insertions(+), 270 deletions(-) create mode 100644 controller/branch_ctl.go diff --git a/api/api_impl/impl.go b/api/api_impl/impl.go index 19e048f6..c69daf8a 100644 --- a/api/api_impl/impl.go +++ b/api/api_impl/impl.go @@ -17,4 +17,5 @@ type APIController struct { controller.WipController controller.CommitController controller.RepositoryController + controller.BranchController } diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 647e31cc..80ff1ad6 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -38,6 +38,12 @@ type AuthenticationToken struct { TokenExpiration *int64 `json:"token_expiration,omitempty"` } +// BranchCreation defines model for BranchCreation. +type BranchCreation struct { + Name string `json:"name"` + Source string `json:"source"` +} + // Change defines model for Change. type Change struct { Action int `json:"Action"` @@ -84,6 +90,33 @@ type ObjectStats struct { // ObjectUserMetadata defines model for ObjectUserMetadata. type ObjectUserMetadata map[string]string +// Pagination defines model for Pagination. +type Pagination struct { + // HasMore Next page is available + HasMore bool `json:"has_more"` + + // MaxPerPage Maximal number of entries per page + MaxPerPage int `json:"max_per_page"` + + // NextOffset Token used to retrieve the next page + NextOffset string `json:"next_offset"` + + // Results Number of values found in the results + Results int `json:"results"` +} + +// Ref defines model for Ref. +type Ref struct { + CommitHash string `json:"CommitHash"` + Name string `json:"Name"` +} + +// RefList defines model for RefList. +type RefList struct { + Pagination Pagination `json:"pagination"` + Results []Ref `json:"results"` +} + // Repository defines model for Repository. type Repository struct { CreatedAt time.Time `json:"CreatedAt"` @@ -155,6 +188,12 @@ type Wip struct { UpdatedAt *time.Time `json:"UpdatedAt,omitempty"` } +// LoginJSONBody defines parameters for Login. +type LoginJSONBody struct { + Password string `json:"password"` + Username string `json:"username"` +} + // DeleteObjectParams defines parameters for DeleteObject. type DeleteObjectParams struct { // WipID working in process @@ -234,12 +273,6 @@ type GetEntriesInCommitParams struct { Ref *string `form:"ref,omitempty" json:"ref,omitempty"` } -// LoginJSONBody defines parameters for Login. -type LoginJSONBody struct { - Password string `json:"password"` - Username string `json:"username"` -} - // DeleteWipParams defines parameters for DeleteWip. type DeleteWipParams struct { // RefName ref name @@ -279,21 +312,24 @@ type CommitWipParams struct { RefName string `form:"refName" json:"refName"` } +// LoginJSONRequestBody defines body for Login for application/json ContentType. +type LoginJSONRequestBody LoginJSONBody + // UploadObjectMultipartRequestBody defines body for UploadObject for multipart/form-data ContentType. type UploadObjectMultipartRequestBody UploadObjectMultipartBody // UpdateRepositoryJSONRequestBody defines body for UpdateRepository for application/json ContentType. type UpdateRepositoryJSONRequestBody = UpdateRepository -// LoginJSONRequestBody defines body for Login for application/json ContentType. -type LoginJSONRequestBody LoginJSONBody - // RegisterJSONRequestBody defines body for Register for application/json ContentType. type RegisterJSONRequestBody = UserRegisterInfo // CreateRepositoryJSONRequestBody defines body for CreateRepository for application/json ContentType. type CreateRepositoryJSONRequestBody = CreateRepository +// CreateBranchJSONRequestBody defines body for CreateBranch for application/json ContentType. +type CreateBranchJSONRequestBody = BranchCreation + // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error @@ -367,6 +403,11 @@ func WithRequestEditorFn(fn RequestEditorFn) ClientOption { // The interface specification for the client above. type ClientInterface interface { + // LoginWithBody request with any body + LoginWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + Login(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // DeleteObject request DeleteObject(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -399,11 +440,6 @@ type ClientInterface interface { // GetEntriesInCommit request GetEntriesInCommit(ctx context.Context, user string, repository string, params *GetEntriesInCommitParams, reqEditors ...RequestEditorFn) (*http.Response, error) - // LoginWithBody request with any body - LoginWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - Login(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // Logout request Logout(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -443,6 +479,44 @@ type ClientInterface interface { // ListWip request ListWip(ctx context.Context, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // ListBranches request + ListBranches(ctx context.Context, user string, reopsitory string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateBranchWithBody request with any body + CreateBranchWithBody(ctx context.Context, user string, reopsitory string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateBranch(ctx context.Context, user string, reopsitory string, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // DeleteBranch request + DeleteBranch(ctx context.Context, user string, repository string, branch string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetBranch request + GetBranch(ctx context.Context, user string, repository string, branch string, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) LoginWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewLoginRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) Login(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewLoginRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) } func (c *Client) DeleteObject(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -577,30 +651,6 @@ func (c *Client) GetEntriesInCommit(ctx context.Context, user string, repository return c.Client.Do(req) } -func (c *Client) LoginWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewLoginRequestWithBody(c.Server, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) Login(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewLoginRequest(c.Server, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - func (c *Client) Logout(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewLogoutRequest(c.Server) if err != nil { @@ -769,6 +819,106 @@ func (c *Client) ListWip(ctx context.Context, repository string, reqEditors ...R return c.Client.Do(req) } +func (c *Client) ListBranches(ctx context.Context, user string, reopsitory string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListBranchesRequest(c.Server, user, reopsitory) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateBranchWithBody(ctx context.Context, user string, reopsitory string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateBranchRequestWithBody(c.Server, user, reopsitory, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateBranch(ctx context.Context, user string, reopsitory string, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateBranchRequest(c.Server, user, reopsitory, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) DeleteBranch(ctx context.Context, user string, repository string, branch string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteBranchRequest(c.Server, user, repository, branch) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetBranch(ctx context.Context, user string, repository string, branch string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetBranchRequest(c.Server, user, repository, branch) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewLoginRequest calls the generic Login builder with application/json body +func NewLoginRequest(server string, body LoginJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewLoginRequestWithBody(server, "application/json", bodyReader) +} + +// NewLoginRequestWithBody generates requests for Login with any type of body +func NewLoginRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/auth/login") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewDeleteObjectRequest generates requests for DeleteObject func NewDeleteObjectRequest(server string, user string, repository string, params *DeleteObjectParams) (*http.Request, error) { var err error @@ -1472,46 +1622,6 @@ func NewGetEntriesInCommitRequest(server string, user string, repository string, return req, nil } -// NewLoginRequest calls the generic Login builder with application/json body -func NewLoginRequest(server string, body LoginJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewLoginRequestWithBody(server, "application/json", bodyReader) -} - -// NewLoginRequestWithBody generates requests for Login with any type of body -func NewLoginRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/users/login") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("POST", queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - // NewLogoutRequest generates requests for Logout func NewLogoutRequest(server string) (*http.Request, error) { var err error @@ -2038,40 +2148,231 @@ func NewListWipRequest(server string, repository string) (*http.Request, error) return req, nil } -func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { - for _, r := range c.RequestEditors { - if err := r(ctx, req); err != nil { - return err - } - } - for _, r := range additionalEditors { - if err := r(ctx, req); err != nil { - return err - } +// NewListBranchesRequest generates requests for ListBranches +func NewListBranchesRequest(server string, user string, reopsitory string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + if err != nil { + return nil, err } - return nil -} -// ClientWithResponses builds on ClientInterface to offer response payloads -type ClientWithResponses struct { - ClientInterface -} + var pathParam1 string -// NewClientWithResponses creates a new ClientWithResponses, which wraps -// Client with return type handling -func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { - client, err := NewClient(server, opts...) + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "reopsitory", runtime.ParamLocationPath, reopsitory) if err != nil { return nil, err } - return &ClientWithResponses{client}, nil -} -// WithBaseURL overrides the baseURL. -func WithBaseURL(baseURL string) ClientOption { - return func(c *Client) error { - newBaseURL, err := url.Parse(baseURL) - if err != nil { + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/%s/%s/branches", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewCreateBranchRequest calls the generic CreateBranch builder with application/json body +func NewCreateBranchRequest(server string, user string, reopsitory string, body CreateBranchJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateBranchRequestWithBody(server, user, reopsitory, "application/json", bodyReader) +} + +// NewCreateBranchRequestWithBody generates requests for CreateBranch with any type of body +func NewCreateBranchRequestWithBody(server string, user string, reopsitory string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "reopsitory", runtime.ParamLocationPath, reopsitory) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/%s/%s/branches", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewDeleteBranchRequest generates requests for DeleteBranch +func NewDeleteBranchRequest(server string, user string, repository string, branch string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "branch", runtime.ParamLocationPath, branch) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/%s/%s/branches/%s", pathParam0, pathParam1, pathParam2) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetBranchRequest generates requests for GetBranch +func NewGetBranchRequest(server string, user string, repository string, branch string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "branch", runtime.ParamLocationPath, branch) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/%s/%s/branches/%s", pathParam0, pathParam1, pathParam2) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { return err } c.Server = newBaseURL.String() @@ -2081,6 +2382,11 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { + // LoginWithBodyWithResponse request with any body + LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) + + LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) + // DeleteObjectWithResponse request DeleteObjectWithResponse(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) @@ -2113,11 +2419,6 @@ type ClientWithResponsesInterface interface { // GetEntriesInCommitWithResponse request GetEntriesInCommitWithResponse(ctx context.Context, user string, repository string, params *GetEntriesInCommitParams, reqEditors ...RequestEditorFn) (*GetEntriesInCommitResponse, error) - // LoginWithBodyWithResponse request with any body - LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) - - LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) - // LogoutWithResponse request LogoutWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*LogoutResponse, error) @@ -2157,6 +2458,42 @@ type ClientWithResponsesInterface interface { // ListWipWithResponse request ListWipWithResponse(ctx context.Context, repository string, reqEditors ...RequestEditorFn) (*ListWipResponse, error) + + // ListBranchesWithResponse request + ListBranchesWithResponse(ctx context.Context, user string, reopsitory string, reqEditors ...RequestEditorFn) (*ListBranchesResponse, error) + + // CreateBranchWithBodyWithResponse request with any body + CreateBranchWithBodyWithResponse(ctx context.Context, user string, reopsitory string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) + + CreateBranchWithResponse(ctx context.Context, user string, reopsitory string, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) + + // DeleteBranchWithResponse request + DeleteBranchWithResponse(ctx context.Context, user string, repository string, branch string, reqEditors ...RequestEditorFn) (*DeleteBranchResponse, error) + + // GetBranchWithResponse request + GetBranchWithResponse(ctx context.Context, user string, repository string, branch string, reqEditors ...RequestEditorFn) (*GetBranchResponse, error) +} + +type LoginResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *AuthenticationToken +} + +// Status returns HTTPResponse.Status +func (r LoginResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r LoginResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 } type DeleteObjectResponse struct { @@ -2374,28 +2711,6 @@ func (r GetEntriesInCommitResponse) StatusCode() int { return 0 } -type LoginResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *AuthenticationToken -} - -// Status returns HTTPResponse.Status -func (r LoginResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r LoginResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - type LogoutResponse struct { Body []byte HTTPResponse *http.Response @@ -2657,6 +2972,109 @@ func (r ListWipResponse) StatusCode() int { return 0 } +type ListBranchesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *RefList +} + +// Status returns HTTPResponse.Status +func (r ListBranchesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ListBranchesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type CreateBranchResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r CreateBranchResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateBranchResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type DeleteBranchResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r DeleteBranchResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DeleteBranchResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetBranchResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Ref +} + +// Status returns HTTPResponse.Status +func (r GetBranchResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetBranchResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// LoginWithBodyWithResponse request with arbitrary body returning *LoginResponse +func (c *ClientWithResponses) LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) { + rsp, err := c.LoginWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseLoginResponse(rsp) +} + +func (c *ClientWithResponses) LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) { + rsp, err := c.Login(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseLoginResponse(rsp) +} + // DeleteObjectWithResponse request returning *DeleteObjectResponse func (c *ClientWithResponses) DeleteObjectWithResponse(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) { rsp, err := c.DeleteObject(ctx, user, repository, params, reqEditors...) @@ -2755,23 +3173,6 @@ func (c *ClientWithResponses) GetEntriesInCommitWithResponse(ctx context.Context return ParseGetEntriesInCommitResponse(rsp) } -// LoginWithBodyWithResponse request with arbitrary body returning *LoginResponse -func (c *ClientWithResponses) LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) { - rsp, err := c.LoginWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseLoginResponse(rsp) -} - -func (c *ClientWithResponses) LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) { - rsp, err := c.Login(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseLoginResponse(rsp) -} - // LogoutWithResponse request returning *LogoutResponse func (c *ClientWithResponses) LogoutWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*LogoutResponse, error) { rsp, err := c.Logout(ctx, reqEditors...) @@ -2896,6 +3297,76 @@ func (c *ClientWithResponses) ListWipWithResponse(ctx context.Context, repositor return ParseListWipResponse(rsp) } +// ListBranchesWithResponse request returning *ListBranchesResponse +func (c *ClientWithResponses) ListBranchesWithResponse(ctx context.Context, user string, reopsitory string, reqEditors ...RequestEditorFn) (*ListBranchesResponse, error) { + rsp, err := c.ListBranches(ctx, user, reopsitory, reqEditors...) + if err != nil { + return nil, err + } + return ParseListBranchesResponse(rsp) +} + +// CreateBranchWithBodyWithResponse request with arbitrary body returning *CreateBranchResponse +func (c *ClientWithResponses) CreateBranchWithBodyWithResponse(ctx context.Context, user string, reopsitory string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) { + rsp, err := c.CreateBranchWithBody(ctx, user, reopsitory, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateBranchResponse(rsp) +} + +func (c *ClientWithResponses) CreateBranchWithResponse(ctx context.Context, user string, reopsitory string, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) { + rsp, err := c.CreateBranch(ctx, user, reopsitory, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateBranchResponse(rsp) +} + +// DeleteBranchWithResponse request returning *DeleteBranchResponse +func (c *ClientWithResponses) DeleteBranchWithResponse(ctx context.Context, user string, repository string, branch string, reqEditors ...RequestEditorFn) (*DeleteBranchResponse, error) { + rsp, err := c.DeleteBranch(ctx, user, repository, branch, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteBranchResponse(rsp) +} + +// GetBranchWithResponse request returning *GetBranchResponse +func (c *ClientWithResponses) GetBranchWithResponse(ctx context.Context, user string, repository string, branch string, reqEditors ...RequestEditorFn) (*GetBranchResponse, error) { + rsp, err := c.GetBranch(ctx, user, repository, branch, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetBranchResponse(rsp) +} + +// ParseLoginResponse parses an HTTP response from a LoginWithResponse call +func ParseLoginResponse(rsp *http.Response) (*LoginResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &LoginResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest AuthenticationToken + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseDeleteObjectResponse parses an HTTP response from a DeleteObjectWithResponse call func ParseDeleteObjectResponse(rsp *http.Response) (*DeleteObjectResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -3106,32 +3577,6 @@ func ParseGetEntriesInCommitResponse(rsp *http.Response) (*GetEntriesInCommitRes return response, nil } -// ParseLoginResponse parses an HTTP response from a LoginWithResponse call -func ParseLoginResponse(rsp *http.Response) (*LoginResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &LoginResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest AuthenticationToken - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - } - - return response, nil -} - // ParseLogoutResponse parses an HTTP response from a LogoutWithResponse call func ParseLogoutResponse(rsp *http.Response) (*LogoutResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -3388,22 +3833,106 @@ func ParseCommitWipResponse(rsp *http.Response) (*CommitWipResponse, error) { return response, nil } -// ParseListWipResponse parses an HTTP response from a ListWipWithResponse call -func ParseListWipResponse(rsp *http.Response) (*ListWipResponse, error) { +// ParseListWipResponse parses an HTTP response from a ListWipWithResponse call +func ParseListWipResponse(rsp *http.Response) (*ListWipResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListWipResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []Wip + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseListBranchesResponse parses an HTTP response from a ListBranchesWithResponse call +func ParseListBranchesResponse(rsp *http.Response) (*ListBranchesResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListBranchesResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest RefList + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseCreateBranchResponse parses an HTTP response from a CreateBranchWithResponse call +func ParseCreateBranchResponse(rsp *http.Response) (*CreateBranchResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateBranchResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseDeleteBranchResponse parses an HTTP response from a DeleteBranchWithResponse call +func ParseDeleteBranchResponse(rsp *http.Response) (*DeleteBranchResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DeleteBranchResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetBranchResponse parses an HTTP response from a GetBranchWithResponse call +func ParseGetBranchResponse(rsp *http.Response) (*GetBranchResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &ListWipResponse{ + response := &GetBranchResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []Wip + var dest Ref if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -3416,6 +3945,9 @@ func ParseListWipResponse(rsp *http.Response) (*ListWipResponse, error) { // ServerInterface represents all server handlers. type ServerInterface interface { + // perform a login + // (POST /auth/login) + Login(ctx context.Context, w *JiaozifsResponse, r *http.Request, body LoginJSONRequestBody) // delete object. Missing objects will not return a NotFound error. // (DELETE /object/{user}/{repository}) DeleteObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params DeleteObjectParams) @@ -3446,9 +3978,6 @@ type ServerInterface interface { // list entries in commit // (GET /repos/{user}/{repository}/contents/) GetEntriesInCommit(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params GetEntriesInCommitParams) - // perform a login - // (POST /users/login) - Login(ctx context.Context, w *JiaozifsResponse, r *http.Request, body LoginJSONRequestBody) // perform a logout // (POST /users/logout) Logout(ctx context.Context, w *JiaozifsResponse, r *http.Request) @@ -3485,12 +4014,30 @@ type ServerInterface interface { // list wip in specific project and user // (GET /wip/{repository}/list) ListWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string) + // list branches + // (GET /{user}/{reopsitory}/branches) + ListBranches(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, reopsitory string) + // create branch + // (POST /{user}/{reopsitory}/branches) + CreateBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateBranchJSONRequestBody, user string, reopsitory string) + // delete branch + // (DELETE /{user}/{repository}/branches/{branch}) + DeleteBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, branch string) + // get branch + // (GET /{user}/{repository}/branches/{branch}) + GetBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, branch string) } // Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. type Unimplemented struct{} +// perform a login +// (POST /auth/login) +func (_ Unimplemented) Login(ctx context.Context, w *JiaozifsResponse, r *http.Request, body LoginJSONRequestBody) { + w.WriteHeader(http.StatusNotImplemented) +} + // delete object. Missing objects will not return a NotFound error. // (DELETE /object/{user}/{repository}) func (_ Unimplemented) DeleteObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params DeleteObjectParams) { @@ -3550,12 +4097,6 @@ func (_ Unimplemented) GetEntriesInCommit(ctx context.Context, w *JiaozifsRespon w.WriteHeader(http.StatusNotImplemented) } -// perform a login -// (POST /users/login) -func (_ Unimplemented) Login(ctx context.Context, w *JiaozifsResponse, r *http.Request, body LoginJSONRequestBody) { - w.WriteHeader(http.StatusNotImplemented) -} - // perform a logout // (POST /users/logout) func (_ Unimplemented) Logout(ctx context.Context, w *JiaozifsResponse, r *http.Request) { @@ -3628,6 +4169,30 @@ func (_ Unimplemented) ListWip(ctx context.Context, w *JiaozifsResponse, r *http w.WriteHeader(http.StatusNotImplemented) } +// list branches +// (GET /{user}/{reopsitory}/branches) +func (_ Unimplemented) ListBranches(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, reopsitory string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// create branch +// (POST /{user}/{reopsitory}/branches) +func (_ Unimplemented) CreateBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateBranchJSONRequestBody, user string, reopsitory string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// delete branch +// (DELETE /{user}/{repository}/branches/{branch}) +func (_ Unimplemented) DeleteBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, branch string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get branch +// (GET /{user}/{repository}/branches/{branch}) +func (_ Unimplemented) GetBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, branch string) { + w.WriteHeader(http.StatusNotImplemented) +} + // ServerInterfaceWrapper converts contexts to parameters. type ServerInterfaceWrapper struct { Handler ServerInterface @@ -3637,6 +4202,31 @@ type ServerInterfaceWrapper struct { type MiddlewareFunc func(http.Handler) http.Handler +// Login operation middleware +func (siw *ServerInterfaceWrapper) Login(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + // ------------- Body parse ------------- + var body LoginJSONRequestBody + parseBody := r.ContentLength != 0 + if parseBody { + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "Error unmarshalling body 'Login' as JSON", http.StatusBadRequest) + return + } + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.Login(r.Context(), &JiaozifsResponse{w}, r, body) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // DeleteObject operation middleware func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -4332,31 +4922,6 @@ func (siw *ServerInterfaceWrapper) GetEntriesInCommit(w http.ResponseWriter, r * handler.ServeHTTP(w, r.WithContext(ctx)) } -// Login operation middleware -func (siw *ServerInterfaceWrapper) Login(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - // ------------- Body parse ------------- - var body LoginJSONRequestBody - parseBody := r.ContentLength != 0 - if parseBody { - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - http.Error(w, "Error unmarshalling body 'Login' as JSON", http.StatusBadRequest) - return - } - } - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.Login(r.Context(), &JiaozifsResponse{w}, r, body) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r.WithContext(ctx)) -} - // Logout operation middleware func (siw *ServerInterfaceWrapper) Logout(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -4816,6 +5381,198 @@ func (siw *ServerInterfaceWrapper) ListWip(w http.ResponseWriter, r *http.Reques handler.ServeHTTP(w, r.WithContext(ctx)) } +// ListBranches operation middleware +func (siw *ServerInterfaceWrapper) ListBranches(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "user" ------------- + var user string + + err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + return + } + + // ------------- Path parameter "reopsitory" ------------- + var reopsitory string + + err = runtime.BindStyledParameterWithOptions("simple", "reopsitory", chi.URLParam(r, "reopsitory"), &reopsitory, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "reopsitory", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListBranches(r.Context(), &JiaozifsResponse{w}, r, user, reopsitory) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// CreateBranch operation middleware +func (siw *ServerInterfaceWrapper) CreateBranch(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Body parse ------------- + var body CreateBranchJSONRequestBody + parseBody := true + if parseBody { + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "Error unmarshalling body 'CreateBranch' as JSON", http.StatusBadRequest) + return + } + } + + // ------------- Path parameter "user" ------------- + var user string + + err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + return + } + + // ------------- Path parameter "reopsitory" ------------- + var reopsitory string + + err = runtime.BindStyledParameterWithOptions("simple", "reopsitory", chi.URLParam(r, "reopsitory"), &reopsitory, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "reopsitory", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CreateBranch(r.Context(), &JiaozifsResponse{w}, r, body, user, reopsitory) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// DeleteBranch operation middleware +func (siw *ServerInterfaceWrapper) DeleteBranch(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "user" ------------- + var user string + + err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + // ------------- Path parameter "branch" ------------- + var branch string + + err = runtime.BindStyledParameterWithOptions("simple", "branch", chi.URLParam(r, "branch"), &branch, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "branch", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteBranch(r.Context(), &JiaozifsResponse{w}, r, user, repository, branch) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// GetBranch operation middleware +func (siw *ServerInterfaceWrapper) GetBranch(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "user" ------------- + var user string + + err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + // ------------- Path parameter "branch" ------------- + var branch string + + err = runtime.BindStyledParameterWithOptions("simple", "branch", chi.URLParam(r, "branch"), &branch, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "branch", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetBranch(r.Context(), &JiaozifsResponse{w}, r, user, repository, branch) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + type UnescapedCookieParamError struct { ParamName string Err error @@ -4934,6 +5691,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl ErrorHandlerFunc: options.ErrorHandlerFunc, } + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/auth/login", wrapper.Login) + }) r.Group(func(r chi.Router) { r.Delete(options.BaseURL+"/object/{user}/{repository}", wrapper.DeleteObject) }) @@ -4964,9 +5724,6 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/repos/{user}/{repository}/contents/", wrapper.GetEntriesInCommit) }) - r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/users/login", wrapper.Login) - }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/users/logout", wrapper.Logout) }) @@ -5003,6 +5760,18 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/wip/{repository}/list", wrapper.ListWip) }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/{user}/{reopsitory}/branches", wrapper.ListBranches) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/{user}/{reopsitory}/branches", wrapper.CreateBranch) + }) + r.Group(func(r chi.Router) { + r.Delete(options.BaseURL+"/{user}/{repository}/branches/{branch}", wrapper.DeleteBranch) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/{user}/{repository}/branches/{branch}", wrapper.GetBranch) + }) return r } @@ -5010,56 +5779,63 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "dW+38sB869XdfwMAAP//t0YWRJNHAAA=", + "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", + "BfOpF3f/CQAA//8NlYT7L1MAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/resp.gen.go b/api/resp.gen.go index b726ce38..0cfccc98 100644 --- a/api/resp.gen.go +++ b/api/resp.gen.go @@ -3,7 +3,7 @@ // // Generated by this command: // -// mockgen --package=api --destination=resp.gen.go net/http ResponseWriter +// mockgen.exe --package=api --destination=resp.gen.go net/http ResponseWriter // // Package api is a generated GoMock package. package api diff --git a/api/swagger.yml b/api/swagger.yml index 404fba9a..ad08cc53 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -31,6 +31,38 @@ components: in: cookie name: internal_auth_session schemas: + BranchCreation: + type: object + required: + - name + - source + properties: + name: + type: string + source: + type: string + Ref: + type: object + required: + - CommitHash + - Name + properties: + CommitHash: + type: string + Name: + type: string + RefList: + type: object + required: + - pagination + - results + properties: + pagination: + $ref: "#/components/schemas/Pagination" + results: + type: array + items: + $ref: "#/components/schemas/Ref" CreateRepository: type: object required: @@ -1058,7 +1090,118 @@ paths: 403: description: Forbidden - /users/login: + /{user}/{reopsitory}/branches: + parameters: + - in: path + name: user + required: true + schema: + type: string + - in: path + name: reopsitory + required: true + schema: + type: string + get: + tags: + - branches + operationId: listBranches + summary: list branches + responses: + 200: + description: branch list + content: + application/json: + schema: + $ref: "#/components/schemas/RefList" + 401: + description: Unauthorized + 404: + description: Resource Not Found + 420: + description: Too many requests + default: + description: Internal Server Error + post: + tags: + - branches + operationId: createBranch + summary: create branch + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/BranchCreation" + responses: + 201: + description: create branch success + 400: + description: ValidationError + 404: + description: Resource Not Found + 409: + description: Resource Conflicts With Target + 420: + description: Too many requests + default: + description: Internal Server Error + + /{user}/{repository}/branches/{branch}: + parameters: + - in: path + name: user + required: true + schema: + type: string + - in: path + name: repository + required: true + schema: + type: string + - in: path + name: branch + required: true + schema: + type: string + get: + tags: + - branches + operationId: getBranch + summary: get branch + responses: + 200: + description: branch + content: + application/json: + schema: + $ref: "#/components/schemas/Ref" + 401: + description: Unauthorized + 404: + description: Resource Not Found + 420: + description: Too many requests + default: + description: Internal Server Error + delete: + tags: + - branches + operationId: deleteBranch + summary: delete branch + responses: + 204: + description: branch delete successfully + 401: + description: Unauthorized + 404: + description: Resource Not Found + 420: + description: Too many requests + default: + description: Internal Server Error + + /auth/login: post: tags: - auth diff --git a/auth/errors.go b/auth/errors.go index 3e824527..378fe11b 100644 --- a/auth/errors.go +++ b/auth/errors.go @@ -3,8 +3,6 @@ package auth import "errors" var ( - ErrComparePassword = errors.New("compare password error") - ErrParseToken = errors.New("parse token error") ErrInvalidToken = errors.New("invalid token") ErrInvalidNameEmail = errors.New("invalid name or email") ErrExtractClaims = errors.New("failed to extract claims from JWT token") diff --git a/controller/branch_ctl.go b/controller/branch_ctl.go new file mode 100644 index 00000000..58eafb2c --- /dev/null +++ b/controller/branch_ctl.go @@ -0,0 +1,158 @@ +package controller + +import ( + "context" + "net/http" + "time" + + logging "github.com/ipfs/go-log/v2" + "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/utils" + "go.uber.org/fx" +) + +var branchLog = logging.Logger("branch_ctl") + +type BranchController struct { + fx.In + + Repo models.IRepo +} + +func (bct BranchController) ListBranches(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repoName string) { + // Get user + user, err := bct.Repo.UserRepo().Get(ctx, &models.GetUserParams{Name: utils.String(userName)}) + if err != nil { + w.Error(err) + return + } + // Get repo + repository, err := bct.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ + CreatorID: user.ID, + Name: utils.String(repoName), + }) + if err != nil { + w.Error(err) + return + } + // List branches + branches, err := bct.Repo.RefRepo().List(ctx, repository.ID) + if err != nil { + w.Error(err) + return + } + var refs []api.Ref + for _, branch := range branches { + ref := api.Ref{ + CommitHash: branch.Name, + Name: branch.CommitHash.Hex(), + } + refs = append(refs, ref) + } + w.JSON(api.RefList{Results: refs}) +} + +func (bct BranchController) CreateBranch(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.CreateBranchJSONRequestBody, userName string, repoName string) { + // Decode request body + bc := api.BranchCreation{ + Name: body.Name, + Source: body.Source, + } + branchLog.Info(bc) + // Get user + user, err := bct.Repo.UserRepo().Get(ctx, &models.GetUserParams{Name: utils.String(userName)}) + if err != nil { + w.Error(err) + return + } + // Get repo + repository, err := bct.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ + CreatorID: user.ID, + Name: utils.String(repoName), + }) + if err != nil { + w.Error(err) + return + } + // Get source ref + params := models.NewGetRefParams() + params.SetName(bc.Source) + params.SetRepositoryID(repository.ID) + ref, err := bct.Repo.RefRepo().Get(ctx, params) + // Create branch + newRef := &models.Ref{ + RepositoryID: repository.ID, + CommitHash: ref.CommitHash, + Name: bc.Name, + Description: ref.Description, + CreatorID: user.ID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + _, err = bct.Repo.RefRepo().Insert(ctx, newRef) + if err != nil { + w.Error(err) + return + } + w.String("Branch created successfully") +} + +func (bct BranchController) DeleteBranch(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repoName string, branch string) { + // Get user + user, err := bct.Repo.UserRepo().Get(ctx, &models.GetUserParams{Name: utils.String(userName)}) + if err != nil { + w.Error(err) + return + } + // Get repo + repository, err := bct.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ + CreatorID: user.ID, + Name: utils.String(repoName), + }) + if err != nil { + w.Error(err) + return + } + // Delete branch + params := models.NewDeleteRefParams() + params.SetName(branch) + params.SetRepositoryID(repository.ID) + err = bct.Repo.RefRepo().Delete(ctx, params) + if err != nil { + w.Error(err) + return + } + w.OK() +} + +func (bct BranchController) GetBranch(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repoName string, branch string) { + // Get user + user, err := bct.Repo.UserRepo().Get(ctx, &models.GetUserParams{Name: utils.String(userName)}) + if err != nil { + w.Error(err) + return + } + // Get repo + repository, err := bct.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ + CreatorID: user.ID, + Name: utils.String(repoName), + }) + if err != nil { + w.Error(err) + return + } + // Get branch + params := models.NewGetRefParams() + params.SetName(branch) + params.SetRepositoryID(repository.ID) + ref, err := bct.Repo.RefRepo().Get(ctx, params) + if err != nil { + w.Error(err) + return + } + w.JSON(api.Ref{ + CommitHash: ref.CommitHash.Hex(), + Name: ref.Name, + }) +} diff --git a/models/ref.go b/models/ref.go index a24f3f63..545676e2 100644 --- a/models/ref.go +++ b/models/ref.go @@ -52,6 +52,26 @@ func (gup *GetRefParams) SetName(name string) *GetRefParams { return gup } +type DeleteRefParams struct { + ID uuid.UUID + RepositoryID uuid.UUID + Name *string +} + +func NewDeleteRefParams() *DeleteRefParams { + return &DeleteRefParams{} +} + +func (gup *DeleteRefParams) SetRepositoryID(repositoryID uuid.UUID) *DeleteRefParams { + gup.RepositoryID = repositoryID + return gup +} + +func (gup *DeleteRefParams) SetName(name string) *DeleteRefParams { + gup.Name = &name + return gup +} + type UpdateRefParams struct { bun.BaseModel `bun:"table:refs"` ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` @@ -71,6 +91,9 @@ type IRefRepo interface { Insert(ctx context.Context, repo *Ref) (*Ref, error) UpdateByID(ctx context.Context, params *UpdateRefParams) error Get(ctx context.Context, id *GetRefParams) (*Ref, error) + + List(ctx context.Context, id uuid.UUID) ([]Ref, error) + Delete(ctx context.Context, params *DeleteRefParams) error } var _ IRefRepo = (*RefRepo)(nil) @@ -110,6 +133,26 @@ func (r RefRepo) Get(ctx context.Context, params *GetRefParams) (*Ref, error) { return repo, query.Limit(1).Scan(ctx, repo) } +func (r RefRepo) List(ctx context.Context, id uuid.UUID) ([]Ref, error) { + var refs []Ref + return refs, r.db.NewSelect().Model(&refs).Where("id = ?", id).Scan(ctx) +} + +func (r RefRepo) Delete(ctx context.Context, params *DeleteRefParams) error { + ref := &Ref{} + query := r.db.NewSelect().Model(ref) + + if uuid.Nil != params.ID { + query = query.Where("id = ?", params.ID) + } + + if uuid.Nil != params.RepositoryID && params.Name != nil { + query = query.Where("repository_id = ? AND name = ?", params.RepositoryID, *params.Name) + } + + return query.Limit(1).Scan(ctx, ref) +} + func (r RefRepo) UpdateByID(ctx context.Context, updateModel *UpdateRefParams) error { _, err := r.db.NewUpdate().Model(updateModel).WherePK().Exec(ctx) return err From 819ab767dcab8b4573b00660bce7dbaeeb02428b Mon Sep 17 00:00:00 2001 From: zjy Date: Thu, 14 Dec 2023 10:47:36 +0800 Subject: [PATCH 074/210] fix: fix get ref err --- controller/branch_ctl.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/controller/branch_ctl.go b/controller/branch_ctl.go index 58eafb2c..c66a5d2d 100644 --- a/controller/branch_ctl.go +++ b/controller/branch_ctl.go @@ -80,6 +80,10 @@ func (bct BranchController) CreateBranch(ctx context.Context, w *api.JiaozifsRes params.SetName(bc.Source) params.SetRepositoryID(repository.ID) ref, err := bct.Repo.RefRepo().Get(ctx, params) + if err != nil { + w.Error(err) + return + } // Create branch newRef := &models.Ref{ RepositoryID: repository.ID, From 2457c5adefe3e136ddf24442f140a2aa9449e7a8 Mon Sep 17 00:00:00 2001 From: brown Date: Thu, 14 Dec 2023 11:39:51 +0800 Subject: [PATCH 075/210] fix: re generate gen-api --- api/resp.gen.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/resp.gen.go b/api/resp.gen.go index 0cfccc98..b726ce38 100644 --- a/api/resp.gen.go +++ b/api/resp.gen.go @@ -3,7 +3,7 @@ // // Generated by this command: // -// mockgen.exe --package=api --destination=resp.gen.go net/http ResponseWriter +// mockgen --package=api --destination=resp.gen.go net/http ResponseWriter // // Package api is a generated GoMock package. package api From 37e2c5e8e2eb744558e490f8a66df057a87e97f4 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Thu, 14 Dec 2023 11:59:46 +0800 Subject: [PATCH 076/210] feat: add setup api --- controller/{version_ctl.go => common_ctl.go} | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) rename controller/{version_ctl.go => common_ctl.go} (54%) diff --git a/controller/version_ctl.go b/controller/common_ctl.go similarity index 54% rename from controller/version_ctl.go rename to controller/common_ctl.go index 35355782..6b008ad2 100644 --- a/controller/version_ctl.go +++ b/controller/common_ctl.go @@ -9,11 +9,11 @@ import ( "go.uber.org/fx" ) -type VersionController struct { +type CommonController struct { fx.In } -func (A VersionController) GetVersion(_ context.Context, w *api.JiaozifsResponse, _ *http.Request) { +func (c CommonController) GetVersion(_ context.Context, w *api.JiaozifsResponse, _ *http.Request) { swagger, err := api.GetSwagger() if err != nil { w.Error(err) @@ -25,3 +25,8 @@ func (A VersionController) GetVersion(_ context.Context, w *api.JiaozifsResponse Version: version.UserVersion(), }) } + +func (c CommonController) GetSetupState(ctx context.Context, w *api.JiaozifsResponse, r *http.Request) { + //TODO implement me + panic("implement me") +} From 1e73c35f8afe3a7edb84597661adc43453f1eeff Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Thu, 14 Dec 2023 12:02:11 +0800 Subject: [PATCH 077/210] fix: merge header and change register url --- api/api_impl/impl.go | 2 +- api/swagger.yml | 69 +++++++++++++++++++++++++++++++++++++++- config/config.go | 15 +++++++++ config/default.go | 14 ++++++++ controller/common_ctl.go | 27 ++++++++++++++-- 5 files changed, 122 insertions(+), 5 deletions(-) diff --git a/api/api_impl/impl.go b/api/api_impl/impl.go index c69daf8a..abdadfe2 100644 --- a/api/api_impl/impl.go +++ b/api/api_impl/impl.go @@ -11,7 +11,7 @@ var _ api.ServerInterface = (*APIController)(nil) type APIController struct { fx.In - controller.VersionController + controller.CommonController controller.ObjectController controller.UserController controller.WipController diff --git a/api/swagger.yml b/api/swagger.yml index ad08cc53..3c392eb0 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -31,6 +31,53 @@ components: in: cookie name: internal_auth_session schemas: + LoginConfig: + type: object + properties: + RBAC: + description: | + RBAC will remain enabled on GUI if "external". That only works + with an external auth service. + type: string + enum: [ simplified, external ] + login_url: + description: primary URL to use for login. + type: string + login_failed_message: + description: | + message to display to users who fail to login; a full sentence that is rendered + in HTML and may contain a link to a secondary login method + type: string + fallback_login_url: + description: secondary URL to offer users to use for login. + type: string + fallback_login_label: + description: label to place on fallback_login_url. + type: string + login_cookie_names: + description: cookie names used to store JWT + type: array + items: + type: string + logout_url: + description: URL to use for logging out. + type: string + required: + - login_url + - login_cookie_names + - logout_url + + SetupState: + type: object + properties: + state: + type: string + enum: [ initialized, not_initialized ] + comm_prefs_missing: + type: boolean + description: true if the comm prefs are missing. + login_config: + $ref: "#/components/schemas/LoginConfig" BranchCreation: type: object required: @@ -433,6 +480,25 @@ paths: schema: $ref: "#/components/schemas/VersionResult" + /setup: + get: + tags: + - common + operationId: getSetupState + summary: check if jiaozifs setup + security: [] + responses: + 200: + description: jiaozifs setup state + content: + application/json: + schema: + $ref: "#/components/schemas/SetupState" + 420: + description: too many requests + 503: + description: service unavailable + /object/{user}/{repository}: parameters: - in: path @@ -1090,6 +1156,7 @@ paths: 403: description: Forbidden + /auth/login: /{user}/{reopsitory}/branches: parameters: - in: path @@ -1240,7 +1307,7 @@ paths: default: description: Internal Server Error - /users/logout: + /auth/logout: post: tags: - auth diff --git a/config/config.go b/config/config.go index 5494619f..742f342f 100644 --- a/config/config.go +++ b/config/config.go @@ -10,6 +10,11 @@ import ( "github.com/spf13/viper" ) +const ( + AuthRBACSimplified = "simplified" + AuthRBACExternal = "external" +) + type Config struct { Path string `mapstructure:"config"` Log LogConfig `mapstructure:"log"` @@ -35,6 +40,16 @@ type DatabaseConfig struct { type AuthConfig struct { SecretKey []byte `mapstructure:"secretKey"` + + UIConfig struct { + RBAC string `mapstructure:"rbac"` + LoginURL string `mapstructure:"login_url"` + LoginFailedMessage string `mapstructure:"login_failed_message"` + FallbackLoginURL *string `mapstructure:"fallback_login_url"` + FallbackLoginLabel *string `mapstructure:"fallback_login_label"` + LoginCookieNames []string `mapstructure:"login_cookie_names"` + LogoutURL string `mapstructure:"logout_url"` + } `mapstructure:"ui_config"` } func InitConfig() error { diff --git a/config/default.go b/config/default.go index bd678e01..e05d3e00 100644 --- a/config/default.go +++ b/config/default.go @@ -29,5 +29,19 @@ var defaultCfg = Config{ }, Auth: AuthConfig{ SecretKey: []byte("THIS_MUST_BE_CHANGED_IN_PRODUCTION"), + UIConfig: struct { + RBAC string `mapstructure:"rbac"` + LoginURL string `mapstructure:"login_url"` + LoginFailedMessage string `mapstructure:"login_failed_message"` + FallbackLoginURL *string `mapstructure:"fallback_login_url"` + FallbackLoginLabel *string `mapstructure:"fallback_login_label"` + LoginCookieNames []string `mapstructure:"login_cookie_names"` + LogoutURL string `mapstructure:"logout_url"` + }{RBAC: AuthRBACSimplified, + LoginURL: "api/v1/login", + LoginFailedMessage: "", + LoginCookieNames: nil, + LogoutURL: "auth/logout", + }, }, } diff --git a/controller/common_ctl.go b/controller/common_ctl.go index 6b008ad2..963f79f8 100644 --- a/controller/common_ctl.go +++ b/controller/common_ctl.go @@ -4,6 +4,9 @@ import ( "context" "net/http" + "github.com/go-openapi/swag" + "github.com/jiaozifs/jiaozifs/config" + "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/version" "go.uber.org/fx" @@ -11,6 +14,8 @@ import ( type CommonController struct { fx.In + + Config *config.Config } func (c CommonController) GetVersion(_ context.Context, w *api.JiaozifsResponse, _ *http.Request) { @@ -26,7 +31,23 @@ func (c CommonController) GetVersion(_ context.Context, w *api.JiaozifsResponse, }) } -func (c CommonController) GetSetupState(ctx context.Context, w *api.JiaozifsResponse, r *http.Request) { - //TODO implement me - panic("implement me") +func newLoginConfig(c *config.AuthConfig) *api.LoginConfig { + return &api.LoginConfig{ + RBAC: (*api.LoginConfigRBAC)(&c.UIConfig.RBAC), + LoginUrl: c.UIConfig.LoginURL, + LoginFailedMessage: &c.UIConfig.LoginFailedMessage, + FallbackLoginUrl: c.UIConfig.FallbackLoginURL, + FallbackLoginLabel: c.UIConfig.FallbackLoginLabel, + LoginCookieNames: c.UIConfig.LoginCookieNames, + LogoutUrl: c.UIConfig.LogoutURL, + } +} + +func (c CommonController) GetSetupState(_ context.Context, w *api.JiaozifsResponse, _ *http.Request) { + state := api.SetupState{ + State: (*api.SetupStateState)(swag.String("initialized")), + LoginConfig: newLoginConfig(&c.Config.Auth), + CommPrefsMissing: swag.Bool(false), + } + w.JSON(state) } From 08a0bf74c8ebcc21415b79875ffd5f0696e1d29c Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Thu, 14 Dec 2023 13:35:56 +0800 Subject: [PATCH 078/210] feat: enchance version api --- version/latest.go | 135 +++++++++++++++++++++++++++++++++++++++++ version/latest_test.go | 61 +++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 version/latest.go create mode 100644 version/latest_test.go diff --git a/version/latest.go b/version/latest.go new file mode 100644 index 00000000..2ec31cf7 --- /dev/null +++ b/version/latest.go @@ -0,0 +1,135 @@ +package version + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "time" + + goversion "github.com/hashicorp/go-version" +) + +const ( + latestVersionTimeout = 10 * time.Second + + DefaultReleasesURL = "https://github.com/treeverse/lakeFS/releases" + githubBaseURL = "https://api.github.com/" + + GithubRepoOwner = "treeverse" + GithubRepoName = "lakeFS" +) + +var ErrHTTPStatus = errors.New("unexpected HTTP status code") + +type RepositoryRelease struct { + TagName string `json:"tag_name,omitempty"` + Name string `json:"name,omitempty"` + Draft bool `json:"draft,omitempty"` + Prerelease bool `json:"prerelease,omitempty"` + ID int64 `json:"id,omitempty"` + URL string `json:"url,omitempty"` +} + +type LatestVersionResponse struct { + CheckTime time.Time `json:"check_time"` + Outdated bool `json:"outdated"` + LatestVersion string `json:"latest_version"` + CurrentVersion string `json:"current_version"` +} + +type Source interface { + FetchLatestVersion() (string, error) +} + +type CachedVersionSource struct { + Source Source + lastCheck time.Time + cachePeriod time.Duration + fetchErr error + fetchResponse string +} + +func NewDefaultVersionSource(cachePeriod time.Duration) Source { + gh := NewGithubReleases(GithubRepoOwner, GithubRepoName) + return NewCachedSource(gh, cachePeriod) +} + +func NewCachedSource(src Source, cachePeriod time.Duration) *CachedVersionSource { + return &CachedVersionSource{ + Source: src, + cachePeriod: cachePeriod, + } +} + +func (cs *CachedVersionSource) FetchLatestVersion() (string, error) { + if time.Since(cs.lastCheck) > cs.cachePeriod { + cs.fetchResponse, cs.fetchErr = cs.Source.FetchLatestVersion() + cs.lastCheck = time.Now() + } + return cs.fetchResponse, cs.fetchErr +} + +type GithubReleases struct { + owner string + repository string +} + +func NewGithubReleases(owner, repository string) *GithubReleases { + return &GithubReleases{ + owner: owner, + repository: repository, + } +} + +func (gh *GithubReleases) FetchLatestVersion() (string, error) { + u, err := url.JoinPath(githubBaseURL, "repos", gh.owner, gh.repository, "releases", "latest") + if err != nil { + return "", err + } + + req, err := http.NewRequest(http.MethodGet, u, nil) + if err != nil { + return "", err + } + + client := &http.Client{ + Timeout: latestVersionTimeout, + } + + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("unexpected HTTP response %d: %w", resp.StatusCode, ErrHTTPStatus) + } + + var release RepositoryRelease + if err := json.NewDecoder(resp.Body).Decode(&release); err != nil { + return "", err + } + + return release.TagName, nil +} + +func CheckLatestVersion(targetVersion string) (*LatestVersionResponse, error) { + targetV, err := goversion.NewVersion(targetVersion) + if err != nil { + return nil, fmt.Errorf("tag parse %s: %w", targetVersion, err) + } + + currentV, err := goversion.NewVersion(UserVersion()) + if err != nil { + return nil, fmt.Errorf("version parse %s: %w", UserVersion(), err) + } + + return &LatestVersionResponse{ + Outdated: currentV.LessThan(targetV), + LatestVersion: targetV.String(), + CurrentVersion: currentV.String(), + }, nil +} diff --git a/version/latest_test.go b/version/latest_test.go new file mode 100644 index 00000000..3f7fca91 --- /dev/null +++ b/version/latest_test.go @@ -0,0 +1,61 @@ +package version_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "github.com/treeverse/lakefs/pkg/version" +) + +type checkLatestVersionTestCase struct { + CurrentVersion string + LatestVersion string + ShouldError bool + ExpectedOutdated bool +} + +func TestCheckLatestVersion(t *testing.T) { + cases := []checkLatestVersionTestCase{ + { + CurrentVersion: version.Version, + LatestVersion: "1.0.0", + ExpectedOutdated: false, + }, + { + CurrentVersion: "0.0.1", + LatestVersion: "1.2.3", + ExpectedOutdated: true, + }, + { + CurrentVersion: "1.2.3", + LatestVersion: "1.2.3", + }, + { + CurrentVersion: "1.2.3", + LatestVersion: "1.0.0", + }, + { + LatestVersion: "abc", + ShouldError: true, + }, + } + for idx, tc := range cases { + t.Run(fmt.Sprintf("check_latest_version_%d", idx), func(t *testing.T) { + version.Version = tc.CurrentVersion + t.Logf("check_latest_version test case input %+v", tc) + latest, err := version.CheckLatestVersion(tc.LatestVersion) + + // assert if should error and quit + if tc.ShouldError { + require.Error(t, err, "expected error when comparing latest versions") + return + } + // success path + require.NoError(t, err, "unexpected error when comparing latest versions") + require.Equal(t, tc.ExpectedOutdated, latest.Outdated, "outdated value not as expected") + require.Equal(t, tc.LatestVersion, latest.LatestVersion, "latest version not as expected") + require.Equal(t, tc.CurrentVersion, latest.CurrentVersion, "current version not as expected") + }) + } +} From b3ec8166b49eb309763e46580c765dbd652713eb Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Thu, 14 Dec 2023 13:36:14 +0800 Subject: [PATCH 079/210] feat: enhance version api --- api/jiaozifs.gen.go | 378 +++++++++++++++++++++++++++++---------- api/swagger.yml | 4 +- cmd/daemon.go | 4 + cmd/version.go | 1 + controller/common_ctl.go | 24 ++- go.mod | 4 +- go.sum | 9 +- makefile | 2 +- version/latest.go | 51 +++++- version/latest_test.go | 12 +- version/version.go | 12 +- 11 files changed, 388 insertions(+), 113 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 80ff1ad6..c3954069 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -29,6 +29,18 @@ const ( Jwt_tokenScopes = "jwt_token.Scopes" ) +// Defines values for LoginConfigRBAC. +const ( + External LoginConfigRBAC = "external" + Simplified LoginConfigRBAC = "simplified" +) + +// Defines values for SetupStateState. +const ( + Initialized SetupStateState = "initialized" + NotInitialized SetupStateState = "not_initialized" +) + // AuthenticationToken defines model for AuthenticationToken. type AuthenticationToken struct { // Token a JWT token that could be used to authenticate requests @@ -72,6 +84,36 @@ type CreateRepository struct { Name string `json:"Name"` } +// LoginConfig defines model for LoginConfig. +type LoginConfig struct { + // RBAC RBAC will remain enabled on GUI if "external". That only works + // with an external auth service. + RBAC *LoginConfigRBAC `json:"RBAC,omitempty"` + + // FallbackLoginLabel label to place on fallback_login_url. + FallbackLoginLabel *string `json:"fallback_login_label,omitempty"` + + // FallbackLoginUrl secondary URL to offer users to use for login. + FallbackLoginUrl *string `json:"fallback_login_url,omitempty"` + + // LoginCookieNames cookie names used to store JWT + LoginCookieNames []string `json:"login_cookie_names"` + + // LoginFailedMessage message to display to users who fail to login; a full sentence that is rendered + // in HTML and may contain a link to a secondary login method + LoginFailedMessage *string `json:"login_failed_message,omitempty"` + + // LoginUrl primary URL to use for login. + LoginUrl string `json:"login_url"` + + // LogoutUrl URL to use for logging out. + LogoutUrl string `json:"logout_url"` +} + +// LoginConfigRBAC RBAC will remain enabled on GUI if "external". That only works +// with an external auth service. +type LoginConfigRBAC string + // ObjectStats defines model for ObjectStats. type ObjectStats struct { Checksum string `json:"checksum"` @@ -128,6 +170,17 @@ type Repository struct { UpdatedAt time.Time `json:"UpdatedAt"` } +// SetupState defines model for SetupState. +type SetupState struct { + // CommPrefsMissing true if the comm prefs are missing. + CommPrefsMissing *bool `json:"comm_prefs_missing,omitempty"` + LoginConfig *LoginConfig `json:"login_config,omitempty"` + State *SetupStateState `json:"state,omitempty"` +} + +// SetupStateState defines model for SetupState.State. +type SetupStateState string + // Signature defines model for Signature. type Signature struct { Email openapi_types.Email `json:"Email"` @@ -169,7 +222,8 @@ type UserRegisterInfo struct { // VersionResult defines model for VersionResult. type VersionResult struct { // ApiVersion runtime version - ApiVersion string `json:"api_version"` + ApiVersion string `json:"api_version"` + LatestVersion *string `json:"latest_version,omitempty"` // Version program version Version string `json:"version"` @@ -408,6 +462,9 @@ type ClientInterface interface { Login(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // Logout request + Logout(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // DeleteObject request DeleteObject(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -440,8 +497,8 @@ type ClientInterface interface { // GetEntriesInCommit request GetEntriesInCommit(ctx context.Context, user string, repository string, params *GetEntriesInCommitParams, reqEditors ...RequestEditorFn) (*http.Response, error) - // Logout request - Logout(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetSetupState request + GetSetupState(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) // RegisterWithBody request with any body RegisterWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -519,6 +576,18 @@ func (c *Client) Login(ctx context.Context, body LoginJSONRequestBody, reqEditor return c.Client.Do(req) } +func (c *Client) Logout(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewLogoutRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) DeleteObject(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewDeleteObjectRequest(c.Server, user, repository, params) if err != nil { @@ -651,8 +720,8 @@ func (c *Client) GetEntriesInCommit(ctx context.Context, user string, repository return c.Client.Do(req) } -func (c *Client) Logout(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewLogoutRequest(c.Server) +func (c *Client) GetSetupState(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetSetupStateRequest(c.Server) if err != nil { return nil, err } @@ -919,6 +988,33 @@ func NewLoginRequestWithBody(server string, contentType string, body io.Reader) return req, nil } +// NewLogoutRequest generates requests for Logout +func NewLogoutRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/auth/logout") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewDeleteObjectRequest generates requests for DeleteObject func NewDeleteObjectRequest(server string, user string, repository string, params *DeleteObjectParams) (*http.Request, error) { var err error @@ -1622,8 +1718,8 @@ func NewGetEntriesInCommitRequest(server string, user string, repository string, return req, nil } -// NewLogoutRequest generates requests for Logout -func NewLogoutRequest(server string) (*http.Request, error) { +// NewGetSetupStateRequest generates requests for GetSetupState +func NewGetSetupStateRequest(server string) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -1631,7 +1727,7 @@ func NewLogoutRequest(server string) (*http.Request, error) { return nil, err } - operationPath := fmt.Sprintf("/users/logout") + operationPath := fmt.Sprintf("/setup") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1641,7 +1737,7 @@ func NewLogoutRequest(server string) (*http.Request, error) { return nil, err } - req, err := http.NewRequest("POST", queryURL.String(), nil) + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } @@ -2387,6 +2483,9 @@ type ClientWithResponsesInterface interface { LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) + // LogoutWithResponse request + LogoutWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*LogoutResponse, error) + // DeleteObjectWithResponse request DeleteObjectWithResponse(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) @@ -2419,8 +2518,8 @@ type ClientWithResponsesInterface interface { // GetEntriesInCommitWithResponse request GetEntriesInCommitWithResponse(ctx context.Context, user string, repository string, params *GetEntriesInCommitParams, reqEditors ...RequestEditorFn) (*GetEntriesInCommitResponse, error) - // LogoutWithResponse request - LogoutWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*LogoutResponse, error) + // GetSetupStateWithResponse request + GetSetupStateWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetSetupStateResponse, error) // RegisterWithBodyWithResponse request with any body RegisterWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterResponse, error) @@ -2496,6 +2595,27 @@ func (r LoginResponse) StatusCode() int { return 0 } +type LogoutResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r LogoutResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r LogoutResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type DeleteObjectResponse struct { Body []byte HTTPResponse *http.Response @@ -2711,13 +2831,14 @@ func (r GetEntriesInCommitResponse) StatusCode() int { return 0 } -type LogoutResponse struct { +type GetSetupStateResponse struct { Body []byte HTTPResponse *http.Response + JSON200 *SetupState } // Status returns HTTPResponse.Status -func (r LogoutResponse) Status() string { +func (r GetSetupStateResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2725,7 +2846,7 @@ func (r LogoutResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r LogoutResponse) StatusCode() int { +func (r GetSetupStateResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -3075,6 +3196,15 @@ func (c *ClientWithResponses) LoginWithResponse(ctx context.Context, body LoginJ return ParseLoginResponse(rsp) } +// LogoutWithResponse request returning *LogoutResponse +func (c *ClientWithResponses) LogoutWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*LogoutResponse, error) { + rsp, err := c.Logout(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseLogoutResponse(rsp) +} + // DeleteObjectWithResponse request returning *DeleteObjectResponse func (c *ClientWithResponses) DeleteObjectWithResponse(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) { rsp, err := c.DeleteObject(ctx, user, repository, params, reqEditors...) @@ -3173,13 +3303,13 @@ func (c *ClientWithResponses) GetEntriesInCommitWithResponse(ctx context.Context return ParseGetEntriesInCommitResponse(rsp) } -// LogoutWithResponse request returning *LogoutResponse -func (c *ClientWithResponses) LogoutWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*LogoutResponse, error) { - rsp, err := c.Logout(ctx, reqEditors...) +// GetSetupStateWithResponse request returning *GetSetupStateResponse +func (c *ClientWithResponses) GetSetupStateWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetSetupStateResponse, error) { + rsp, err := c.GetSetupState(ctx, reqEditors...) if err != nil { return nil, err } - return ParseLogoutResponse(rsp) + return ParseGetSetupStateResponse(rsp) } // RegisterWithBodyWithResponse request with arbitrary body returning *RegisterResponse @@ -3367,6 +3497,22 @@ func ParseLoginResponse(rsp *http.Response) (*LoginResponse, error) { return response, nil } +// ParseLogoutResponse parses an HTTP response from a LogoutWithResponse call +func ParseLogoutResponse(rsp *http.Response) (*LogoutResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &LogoutResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + // ParseDeleteObjectResponse parses an HTTP response from a DeleteObjectWithResponse call func ParseDeleteObjectResponse(rsp *http.Response) (*DeleteObjectResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -3577,19 +3723,29 @@ func ParseGetEntriesInCommitResponse(rsp *http.Response) (*GetEntriesInCommitRes return response, nil } -// ParseLogoutResponse parses an HTTP response from a LogoutWithResponse call -func ParseLogoutResponse(rsp *http.Response) (*LogoutResponse, error) { +// ParseGetSetupStateResponse parses an HTTP response from a GetSetupStateWithResponse call +func ParseGetSetupStateResponse(rsp *http.Response) (*GetSetupStateResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &LogoutResponse{ + response := &GetSetupStateResponse{ Body: bodyBytes, HTTPResponse: rsp, } + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest SetupState + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + return response, nil } @@ -3948,6 +4104,9 @@ type ServerInterface interface { // perform a login // (POST /auth/login) Login(ctx context.Context, w *JiaozifsResponse, r *http.Request, body LoginJSONRequestBody) + // perform a logout + // (POST /auth/logout) + Logout(ctx context.Context, w *JiaozifsResponse, r *http.Request) // delete object. Missing objects will not return a NotFound error. // (DELETE /object/{user}/{repository}) DeleteObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params DeleteObjectParams) @@ -3978,9 +4137,9 @@ type ServerInterface interface { // list entries in commit // (GET /repos/{user}/{repository}/contents/) GetEntriesInCommit(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params GetEntriesInCommitParams) - // perform a logout - // (POST /users/logout) - Logout(ctx context.Context, w *JiaozifsResponse, r *http.Request) + // check if jiaozifs setup + // (GET /setup) + GetSetupState(ctx context.Context, w *JiaozifsResponse, r *http.Request) // perform user registration // (POST /users/register) Register(ctx context.Context, w *JiaozifsResponse, r *http.Request, body RegisterJSONRequestBody) @@ -4038,6 +4197,12 @@ func (_ Unimplemented) Login(ctx context.Context, w *JiaozifsResponse, r *http.R w.WriteHeader(http.StatusNotImplemented) } +// perform a logout +// (POST /auth/logout) +func (_ Unimplemented) Logout(ctx context.Context, w *JiaozifsResponse, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + // delete object. Missing objects will not return a NotFound error. // (DELETE /object/{user}/{repository}) func (_ Unimplemented) DeleteObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params DeleteObjectParams) { @@ -4097,9 +4262,9 @@ func (_ Unimplemented) GetEntriesInCommit(ctx context.Context, w *JiaozifsRespon w.WriteHeader(http.StatusNotImplemented) } -// perform a logout -// (POST /users/logout) -func (_ Unimplemented) Logout(ctx context.Context, w *JiaozifsResponse, r *http.Request) { +// check if jiaozifs setup +// (GET /setup) +func (_ Unimplemented) GetSetupState(ctx context.Context, w *JiaozifsResponse, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } @@ -4227,6 +4392,27 @@ func (siw *ServerInterfaceWrapper) Login(w http.ResponseWriter, r *http.Request) handler.ServeHTTP(w, r.WithContext(ctx)) } +// Logout operation middleware +func (siw *ServerInterfaceWrapper) Logout(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.Logout(r.Context(), &JiaozifsResponse{w}, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // DeleteObject operation middleware func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -4922,18 +5108,12 @@ func (siw *ServerInterfaceWrapper) GetEntriesInCommit(w http.ResponseWriter, r * handler.ServeHTTP(w, r.WithContext(ctx)) } -// Logout operation middleware -func (siw *ServerInterfaceWrapper) Logout(w http.ResponseWriter, r *http.Request) { +// GetSetupState operation middleware +func (siw *ServerInterfaceWrapper) GetSetupState(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - - ctx = context.WithValue(ctx, Basic_authScopes, []string{}) - - ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.Logout(r.Context(), &JiaozifsResponse{w}, r) + siw.Handler.GetSetupState(r.Context(), &JiaozifsResponse{w}, r) })) for _, middleware := range siw.HandlerMiddlewares { @@ -5694,6 +5874,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/auth/login", wrapper.Login) }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/auth/logout", wrapper.Logout) + }) r.Group(func(r chi.Router) { r.Delete(options.BaseURL+"/object/{user}/{repository}", wrapper.DeleteObject) }) @@ -5725,7 +5908,7 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Get(options.BaseURL+"/repos/{user}/{repository}/contents/", wrapper.GetEntriesInCommit) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/users/logout", wrapper.Logout) + r.Get(options.BaseURL+"/setup", wrapper.GetSetupState) }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/users/register", wrapper.Register) @@ -5779,63 +5962,70 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "BfOpF3f/CQAA//8NlYT7L1MAAA==", + "H4sIAAAAAAAC/+Q861LcOJevovJ+VTuT7RuQSX3D1NRUIGTCLslQQCY/Atulto+7ldiSRpJpeijefUuS", + "75bdbmgCmf2TIrZ0dO43nfat57OYMwpUSW//1pP+AmJs/nydqAVQRXysCKMX7CtQ/ZgLxkEoAmaRyh4H", + "IH1BuF7q7XsY/fenC2ReIrXACvksiQI0A5RICJBiCBfQAQn4KwGppDfw1IqDt+9JJQide3cDe8IUbjgR", + "2EKvH/aRkht0xJm/QIQiCT6jgQYVMhFj5e17hKpXLwvYhCqYg/Du7gaePpkICLz9zyktV/k6NvsCvtI4", + "HAhM/cWhgByDKhcojsFwo468ZInwXa9qRxsA+XIXCocLTOfQPPq1n6FUJtdB7MA7wBLeYblwYnqKlfvF", + "BWvZUyPBABhk+DhJYHFMlIOERC2Y0H/9S0Do7Xv/MS6Ucpxq5PiczClWiYAClIINd2kBQvBaVdgVYAVD", + "RYwAGtS38us9iDlc4HnLSynxHFoYLYAqDddSTxTE0rkyfYCFwCsjCQHt8rswD6pa8G+nGnzkwWZcqAna", + "oJAeOMiEVxZJiTkFK0ro13hQlksZO6cKmZVnwJkkiolVU5nelF2Dg08f3KZao9GsciFwwuaEHjIaknnz", + "7LOD14dN96SfoiWJIiQgxoQioHgWQYAYRb9/PEYkRJce3CgQFEeX3gihC+0xGY1WaMnEV3lJl0QtEKYo", + "W2W8J5IgrokPo0vqDTygSawxlyTmEQkJBPphur5ESsGJEEfRDPtfp5GmaRrhGURN7M1j7bB5hH3QONf2", + "JSIaeevBJ8IB3PpqLFbo49mJPoSFIQgdI4TU/00koJAJZEA4T7HAfca+EphqLyqbp9i3yLzN449UTICO", + "Ut5gAxO0x4WYRBBM48LKqwemL/QxAZE8wquUGCHRcsGQ3q+fGGi/IIzCJIqQBKqA+mADJpFIAA1AQHBJ", + "CUXvLt6fIEwDFOMV8hlVWpMwigj9asIpKnhpwKIY1IIFRjdauOYUCRckLgmklwRYotzAmkDmhM4RS9Ro", + "rZspcHRKuXKwy1L/MH+dK2wTm6ql+gvwv0ptMQ6ha+4CVVOVutQqTRYuiiEgGCnrBBsgYlA4wAqvC08W", + "2EcJ4n22Q+82fnh7ec7A423RXb+Yxiyoxo6EULW364Qkyd8wna2U5eOmKVbO9xSlFIGUjZbudmFW+LR/", + "6+EgIJo3ODqtJqUtZlzAO8VzQluSuQWW05gJhwA+wI1CXFs2kQhfYxJpR15QPWMsAkyNCPHNlIOYcqeD", + "eI9vSIwjRJN4BgKxEAFVgoBEHIQ5QXODUBJrFZ245EDhRk1ZGEpQTfgmWc9dnQAN+1o7FkA0o8GltgJk", + "EimHC/2QI3qNowQkCllCA62GGma2rRvnmirkbK4xq8CiSqRLLc60ZdXlZxOR1kSpXwJQAjJozwbOIDwh", + "0pHS8op+dXmAkiZWBZAHpa7dmgGNMFUjpYRLcYCbmva06h6ps9nCxPGbqmtJSOBavS5vewc4cL7oCf9D", + "W4n20HT4+E2mISmSZco3yW3PQSVcxytHleezOJ5yAaGcxkRKjUfDRpVIQCeT2iL1emTWIywApXtGTleV", + "Bdcsp+3St3L6q4NBhm2WfRJKFMER+dukn5SpafnJlYuXTT7kJVuDDUcxJlFFTmCebCLvTwug9xR1KuWj", + "9EwDySVJXekcUeWyo/Z6coMY3O7CGqhYlbt/weSEKUEc05A51HRzL+EnQteCWujH9N4bj0+r2Qi/funa", + "A/31J8LyHkgVu3pilBj5bHKELiNorxiWr8wIv2oR5hnMiVRtQt2AaRxLuWTCOOqY0BOgc513/nu7ZJTO", + "cVH0JwhJGD0zka5JDuZkem2XNH2oSKjmO8oWOEWsQKoyiMaSVvBcsLnAcTv4GunFujLWLqI/Ed4k9QBL", + "KJpu7gD9mDH90JqodoePEroLt9YTUh5d1/ZK75MV1ISi4yP4iSBqda7Dp5XJDEviT3Fi6zETV0081o8L", + "qAuluC1FTcmbLSdFO0OHV8MXg7WgODKrphJkVbUwJ/8DpnnxZammeb9+BliAeJtRZhshBTrmbR0fTRJJ", + "fURVsb8QzP4moUTvLi5O0evTY12fEx+ohKI/7r3m2F8A2h1NvIFnGgYGsNwfj5fL5Qib1yMm5uN0rxyf", + "HB8efTg/Gu6OJqOFiiOT7RIVQflQe15udd7OaDKa6JWMA8WcePvennlky00jh7Hm1tjkPsZwmE3jtfmY", + "XPk48PZtt8+zNglSHbBgZbMx0yCw3oRH6Q3J+Iu0Nm+TJVdRUHjH7fjDDj94Z7dJzjQfNdTdyWQj5Lvy", + "QNfdkDmx1t9LfB+kDJPINpC8gbcAHIAwCJ2DGh5aZa4cDDc45lGrav+KZ34AO7t7P736BZ1itfh1/At6", + "pxT/g0Yrh2FqtF5Odlz9FGya1zo3RX/iiASGmiMhmPEBL3cnjiybMRRjuirurAzVIU6DTXX1cUoAOgdx", + "DQKlsEuuwdv/fDXwZBLHWGdnHgeh3Q3COccUnkstd+MErvTeXHdZojqVV793a0GXnPSu58kzN5cslQ42", + "WVsY32qLuRvfijxc3NlTI7DRoMq3N+a57TgZGxM4BmV09nMd1yUTXwmdI0IRF0zz0BtYL/1XAmJVOOkl", + "4aYcLAxZl2uDktavCV53Vw1BvmzyzlKMLGkBKuQarXqJ1C7aay56y8SMBAFQu8Jx9Aem3rKEBptoQUWm", + "FmlkSRih97ZmTf8v7Q0KZQoJUImgCKPsRARaQ0YlHUj3eFd3A28ODtv4HVQ/AR+sFCCBqe3mZ500c7WS", + "OSnTDP11MtyZ7O5l0rderhD/mbm8LYubY6XV3Nv3/tcC+OGHy8vgxVD/M/gN/fbjf/34r15a0OXUma9A", + "DaUSgOOqj821bUYoFk63OXDrVnZUxZUf2ofDLON3HtXRIz5Kb1KLXc0YeIKlGr5ngb3c6lysl+9OXn0r", + "znAsFMERekwOZfvPsjGAB6vSo3B9b7LruAGFgAjNGXNRxQUMJZlTCMwlU8iEaVmxzB5LTDthft5E7T63", + "p2drd5nas4S5/9qZtC40kygpvJ1XLmKNd4MAGVFpL4XOsSIyJOa24L7ucQ6qqWAuh7dIO6VVj/cOcPCP", + "cnktwiF2jOi78E19vAgyBdf/R1fyjzTpjrw3K3bMXAcIm9XUnIC5P0UkRHV9dzmCmpUTq2Tm1jW1UZ0Y", + "dyalDSE6wRSJ9abAqhyYmTE77XXsrWLYkkzbdQ87S0CEFbmG9aeltPY/62rQUpF95BHr64W/YWVheMMF", + "+FgV26vYpJ28aIVkwjkTStr5pEvvxaVnwnoUsSVKDIEabUwzFTXrtMZSQAEDSf8zVVu0AjW6pG/yowdI", + "LYhEPuZ4RiKiVkXOP4PsYDC3zmGiEqGFFgGWINMRqDw+vWgLSsfh8AOjMHyPlVEgp+e7vHzRGof69IEe", + "klsOvDiJFNGxYKxXD7NZh7amUgmH2pyK5jtGuoaKAIUkAjNcYEWElgviL1CcSMNbzZ0AXWbALr1Reayk", + "A9keTaedrTWdyhM97fVJXBqkeelKFVxdi3u1Ou5XJyciqkcmR8p8Ksx4jxlveWvGzTZMHBsBYeDdDK9z", + "KoZw40dJAMOZ0WVt86ZlYlz5/TomZ9Uo0LPnZIbkbOVfCiPblF0fWbkaEZWolrFTP+xsK6zlwlZMoXSK", + "wxJ0qfBcmFnDxcHJ556mdITz2t36/S8KumTdOOaueiNgbHdDi7PXzs9GSZroNPSk0zlptsXEzk21Gaa9", + "CJXHtCKwzhxMQPiDzTXHCs9/ROmtiysLExCm0yGdivQgh9BrIiy9720OhTm9RMa3ps2mb3Sy9Z2b71rN", + "4VjA+HaGJeh08W69Er0hYbhOdyQHn4TER5qIgU6AddDPn6ZN7GzsU3OZMdVdgTy1ZtlfIPXQLKs7KNBs", + "2rZf+cnlV9KKOa+gXaVzodQGMRBAfSiXzvbl91I5O4BlGrxd8zAqJMddVnFktfiYps7nOZnGoPX09orf", + "vnlicyuGCHtb3DcP4v1KnIoZRrryL8nWz1TmOzRDYzsSVMK7rKM01/uIFUHpFId+5KMyBltk53Y3alu2", + "+l3iA0po8WuIruGGvH1ZxacmfUbTfM/8Ymos0unE9kmHbH7xsRLw+ohke5+jnj/qTRbR7C6+1UYPcIDS", + "RjMalvoN6KlmKzT3UZkE95BFJiTO2tPvEyK3WBj3/GVEubRZ5z+NV3ou1VAdGVfS7bSDxq9jH8ceGsf0", + "Kkh3nlrGFJbPRsR2UH5twWttywS5jgCTz+M/YnjJz3Aw9ryYHjOX1KF1HXZ5T+49yGPpUwm1/WrtMVn6", + "I5j80iJi8zkEQ2J+Eyfcfqw0tN3G6D/zcexH43N1eN01a1IbIa9yIs2ds0WYBsgx0u6KtUvCN+z3fiL8", + "no3eJeFPa34CYnYNyHnHlnFHI9nV6G0nfyuKoME7xO9A+cnbu73YuD6X39YtsoCwX6duCy1gGwqtKnRf", + "5xLehRS9B0aPds/WW/VQ+nuvJ6g7f3LNmfWaoLCRt4fOurzi2DdNsM5G8yfCD9NV6/vL29bUQXOqyNjY", + "k7QV+3QT++lZys9n6Opy3L6hy7tyq2bxeaV/pq819PXwtWmXN86/PORCLZbzJ9P9Fgeb4p3lSyZ5S1FA", + "5gtAuoJ5itzpIe7W0uSwG8VQPsfVw/FG6XcWWvsLW8jHehWdn6wA1lWbzy1RM00FnYUQWrTauWBmZkar", + "Wq0y+jZOrLhyYDyTtNUK6O4mHWSLHnXIwn7dwyHddFLRKOWDRlXPwH7zz8yUdv9+5mLrnT6jE7OCk5ns", + "80ffuAmf6cA2c/OD8qzotjthte9C9u+D1Ry/zUlTpVrXKHa6i/6qNfm5Y+kho2FEfCXRJx1wLrDQFvjt", + "NLLCCbdGVt1GESCyJeNb+1ePDkZJN9b9sC6VTdrAuOfv6p6b/afUdHG7vf/Rxryt+t923/sds10XEt08", + "/07nDzYfy7+q3Rbelj9S8PlKH1T+YIJ9UvkowucrHQFsBuoqBko3jTZJpQFn9qsPxRcI9sfjiPk4WjCp", + "9vde/ryzN8acjK93vGbJsxZgvvXq7v8CAAD//+QRsh5eWQAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 3c392eb0..cd1e49f8 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -387,7 +387,8 @@ components: api_version: type: string description: runtime version - + latest_version: + type: string ObjectUserMetadata: type: object additionalProperties: @@ -1156,7 +1157,6 @@ paths: 403: description: Forbidden - /auth/login: /{user}/{reopsitory}/branches: parameters: - in: path diff --git a/cmd/daemon.go b/cmd/daemon.go index 26b192cb..305be222 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -3,6 +3,8 @@ package cmd import ( "context" + "github.com/jiaozifs/jiaozifs/version" + "github.com/gorilla/sessions" "github.com/jiaozifs/jiaozifs/auth" @@ -45,6 +47,8 @@ var daemonCmd = &cobra.Command{ stop, err := fx_opt.New(cmd.Context(), fx_opt.Override(new(context.Context), cmd.Context()), fx_opt.Override(new(utils.Shutdown), shutdown), + //version + fx_opt.Override(new(version.IChecker), version.NewVersionChecker), //config fx_opt.Override(new(*config.Config), cfg), fx_opt.Override(new(*config.APIConfig), &cfg.API), diff --git a/cmd/version.go b/cmd/version.go index 20d51ced..d77bda07 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -43,6 +43,7 @@ var versionCmd = &cobra.Command{ fmt.Println("Runtime Version ", okResp.JSON200.Version) fmt.Println("Runtime API Version ", okResp.JSON200.ApiVersion) + fmt.Println("LatestVersion Version ", okResp.JSON200.LatestVersion) return nil }, } diff --git a/controller/common_ctl.go b/controller/common_ctl.go index 963f79f8..3e4b9b1e 100644 --- a/controller/common_ctl.go +++ b/controller/common_ctl.go @@ -4,6 +4,10 @@ import ( "context" "net/http" + logging "github.com/ipfs/go-log/v2" + + "github.com/jiaozifs/jiaozifs/utils" + "github.com/go-openapi/swag" "github.com/jiaozifs/jiaozifs/config" @@ -12,10 +16,13 @@ import ( "go.uber.org/fx" ) +var commonLog = logging.Logger("common") + type CommonController struct { fx.In - Config *config.Config + VersionChecker version.IChecker + Config *config.Config } func (c CommonController) GetVersion(_ context.Context, w *api.JiaozifsResponse, _ *http.Request) { @@ -25,9 +32,20 @@ func (c CommonController) GetVersion(_ context.Context, w *api.JiaozifsResponse, return } + latestVersionResp, err := c.VersionChecker.CheckLatestVersion() + if err == nil { + commonLog.Errorf("fetch latest version failed: %v", err) + } + + var latestVersion *string + if latestVersionResp != nil { + latestVersion = utils.String(latestVersionResp.LatestVersion) + } + w.JSON(api.VersionResult{ - ApiVersion: swagger.Info.Version, - Version: version.UserVersion(), + ApiVersion: swagger.Info.Version, + Version: version.UserVersion(), + LatestVersion: latestVersion, }) } diff --git a/go.mod b/go.mod index 4e6cd264..5e8a87e5 100644 --- a/go.mod +++ b/go.mod @@ -30,6 +30,7 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.4.0 github.com/gorilla/sessions v1.2.2 + github.com/hashicorp/go-version v1.6.0 github.com/hnlq715/golang-lru v0.4.0 github.com/ipfs/go-log/v2 v2.5.1 github.com/matoous/go-nanoid/v2 v2.0.0 @@ -49,6 +50,7 @@ require ( github.com/stretchr/testify v1.8.4 github.com/thanhpk/randstr v1.0.6 github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc + github.com/treeverse/lakefs v1.3.1 github.com/uptrace/bun v1.1.16 github.com/uptrace/bun/dialect/pgdialect v1.1.16 github.com/uptrace/bun/driver/pgdriver v1.1.16 @@ -100,7 +102,6 @@ require ( github.com/fatih/color v1.15.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v5 v5.0.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -182,6 +183,7 @@ require ( google.golang.org/grpc v1.58.3 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect mellium.im/sasl v0.3.1 // indirect ) diff --git a/go.sum b/go.sum index b432205d..631697b2 100644 --- a/go.sum +++ b/go.sum @@ -46,6 +46,7 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f cloud.google.com/go/storage v1.33.0 h1:PVrDOkIC8qQVa1P3SXGpQvfuJhN2LHOoyZvWs8D2X5M= cloud.google.com/go/storage v1.33.0/go.mod h1:Hhh/dogNRGca7IWv1RC2YqEn0c0G77ctA/OxflYkiD8= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0 h1:fb8kj/Dh4CSwgsOzHeZY4Xh68cFVbzXx+ONXGMY//4w= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0/go.mod h1:uReU2sSxZExRPBAg3qKzmAucSi51+SP1OhohieR821Q= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 h1:BMAjVKJM0U/CYF27gA0ZMmXGkOcvfFtD0oHVZ1TIPRI= @@ -60,6 +61,7 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg6 github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 h1:WpB/QDNLpMw72xHJc34BNNykqSOeEJDAWkhf0u12/Jk= github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/MadAppGang/httplog v1.3.0 h1:1XU54TO8kiqTeO+7oZLKAM3RP/cJ7SadzslRcKspVHo= github.com/MadAppGang/httplog v1.3.0/go.mod h1:gpYEdkjh/Cda6YxtDy4AB7KY+fR7mb3SqBZw74A5hJ4= @@ -190,7 +192,6 @@ github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+ github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= -github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= @@ -283,6 +284,8 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -458,6 +461,8 @@ github.com/thanhpk/randstr v1.0.6 h1:psAOktJFD4vV9NEVb3qkhRSMvYh4ORRaj1+w/hn4B+o github.com/thanhpk/randstr v1.0.6/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/treeverse/lakefs v1.3.1 h1:oCxDq0ODIl/i/QBl9eeGIl/Rx7KCWlQEIm88IiS+JMg= +github.com/treeverse/lakefs v1.3.1/go.mod h1:YHY5WiQ8RX+m3QtVyZS/34NqEzmg2GyxuY88sUrrfRY= github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= @@ -850,6 +855,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/makefile b/makefile index 4920ee18..bf2223e9 100644 --- a/makefile +++ b/makefile @@ -6,7 +6,7 @@ GOGENERATE=$(GOCMD) generate all: build .PHONY: all -ldflags=-X=github.com/jiaozofs/jiaozifs/version.CurrentCommit=+git.$(subst -,.,$(shell git describe --always --match=NeVeRmAtCh --dirty 2>/dev/null || git rev-parse --short HEAD 2>/dev/null)) +ldflags=-X=github.com/jiaozifs/jiaozifs/version.CurrentCommit=+git.$(subst -,.,$(shell git describe --always --match=NeVeRmAtCh --dirty 2>/dev/null || git rev-parse --short HEAD 2>/dev/null)) ifneq ($(strip $(LDFLAGS)),) ldflags+=-extldflags=$(LDFLAGS) endif diff --git a/version/latest.go b/version/latest.go index 2ec31cf7..f369f927 100644 --- a/version/latest.go +++ b/version/latest.go @@ -14,11 +14,11 @@ import ( const ( latestVersionTimeout = 10 * time.Second - DefaultReleasesURL = "https://github.com/treeverse/lakeFS/releases" + DefaultReleasesURL = "https://github.com/jiaozifs/jiaozifs/releases" githubBaseURL = "https://api.github.com/" - GithubRepoOwner = "treeverse" - GithubRepoName = "lakeFS" + GithubRepoOwner = "jiaozifs" + GithubRepoName = "jiaozifs" ) var ErrHTTPStatus = errors.New("unexpected HTTP status code") @@ -122,6 +122,14 @@ func CheckLatestVersion(targetVersion string) (*LatestVersionResponse, error) { return nil, fmt.Errorf("tag parse %s: %w", targetVersion, err) } + if IsVersionUnreleased() { + return &LatestVersionResponse{ + Outdated: false, + LatestVersion: targetV.String(), + CurrentVersion: UserVersion(), + }, nil + } + currentV, err := goversion.NewVersion(UserVersion()) if err != nil { return nil, fmt.Errorf("version parse %s: %w", UserVersion(), err) @@ -130,6 +138,41 @@ func CheckLatestVersion(targetVersion string) (*LatestVersionResponse, error) { return &LatestVersionResponse{ Outdated: currentV.LessThan(targetV), LatestVersion: targetV.String(), - CurrentVersion: currentV.String(), + CurrentVersion: UserVersion(), }, nil } + +type IChecker interface { + CheckLatestVersion() (*LatestVersionResponse, error) +} + +type Checker struct { + Client http.Client + Version string + latestReleases Source +} + +func NewVersionChecker() *Checker { + return &Checker{ + Client: http.Client{}, + Version: UserVersion(), + latestReleases: NewDefaultVersionSource(time.Hour), + } +} + +// CheckLatestVersion will return the latest version of the current package compared to the current version +func (a *Checker) CheckLatestVersion() (*LatestVersionResponse, error) { + if a == nil || a.latestReleases == nil { + return &LatestVersionResponse{}, nil + } + + latest, err := a.latestReleases.FetchLatestVersion() + if err != nil { + return nil, err + } + result, err := CheckLatestVersion(latest) + if err != nil { + return nil, err + } + return result, nil +} diff --git a/version/latest_test.go b/version/latest_test.go index 3f7fca91..cfe1b2a7 100644 --- a/version/latest_test.go +++ b/version/latest_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" + "github.com/jiaozifs/jiaozifs/version" "github.com/stretchr/testify/require" - "github.com/treeverse/lakefs/pkg/version" ) type checkLatestVersionTestCase struct { @@ -18,21 +18,21 @@ type checkLatestVersionTestCase struct { func TestCheckLatestVersion(t *testing.T) { cases := []checkLatestVersionTestCase{ { - CurrentVersion: version.Version, + CurrentVersion: version.UserVersion(), LatestVersion: "1.0.0", ExpectedOutdated: false, }, { - CurrentVersion: "0.0.1", + CurrentVersion: "v0.0.1", LatestVersion: "1.2.3", ExpectedOutdated: true, }, { - CurrentVersion: "1.2.3", + CurrentVersion: "v1.2.3", LatestVersion: "1.2.3", }, { - CurrentVersion: "1.2.3", + CurrentVersion: "v1.2.3", LatestVersion: "1.0.0", }, { @@ -42,7 +42,7 @@ func TestCheckLatestVersion(t *testing.T) { } for idx, tc := range cases { t.Run(fmt.Sprintf("check_latest_version_%d", idx), func(t *testing.T) { - version.Version = tc.CurrentVersion + version.BuildVersion = tc.CurrentVersion t.Logf("check_latest_version test case input %+v", tc) latest, err := version.CheckLatestVersion(tc.LatestVersion) diff --git a/version/version.go b/version/version.go index 1196d419..5e89d3bf 100644 --- a/version/version.go +++ b/version/version.go @@ -1,12 +1,22 @@ package version +import ( + "regexp" +) + // CurrentCommit current program commit var CurrentCommit string // BuildVersion program version -const BuildVersion = "0.0.1" +var BuildVersion = "dev" // UserVersion return build version and current commit func UserVersion() string { return BuildVersion + CurrentCommit } + +var versionRegex, _ = regexp.Compile(`^v[0-9]+\.[0-9]+\.[0-9]?`) + +func IsVersionUnreleased() bool { + return !versionRegex.Match([]byte(UserVersion())) +} From d1a1a9f748596dc8d3c947d7ca74393d8ba398b2 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Sun, 17 Dec 2023 16:16:55 +0800 Subject: [PATCH 080/210] feat: add tree node properties --- api/jiaozifs.gen.go | 129 +++-- api/swagger.yml | 27 +- controller/commit_ctl.go | 6 +- controller/object_ctl.go | 20 +- controller/repository_ctl.go | 5 +- controller/wip_ctl.go | 8 +- go.mod | 7 +- go.sum | 10 +- models/commit.go | 132 +++++ models/commit_test.go | 29 + models/filemode/filemode.go | 19 + models/filemode/filemode_test.go | 15 + .../migrations/20210505110026_init_project.go | 18 +- models/object.go | 437 --------------- models/repo.go | 16 +- models/repo_test.go | 4 +- models/tag.go | 64 +++ models/tag_test.go | 31 ++ models/tree.go | 313 +++++++++++ models/{object_test.go => tree_test.go} | 29 +- versionmgr/changes.go | 4 +- versionmgr/changes_test.go | 9 +- versionmgr/commit.go | 69 +-- versionmgr/commit_node.go | 54 +- versionmgr/commit_test.go | 46 +- versionmgr/merge_base_test.go | 13 +- versionmgr/merkletrie/README.md | 1 + versionmgr/merkletrie/change.go | 149 +++++ versionmgr/merkletrie/change_test.go | 76 +++ versionmgr/merkletrie/difftree.go | 486 ++++++++++++++++ versionmgr/merkletrie/difftree_test.go | 523 ++++++++++++++++++ versionmgr/merkletrie/doc.go | 34 ++ versionmgr/merkletrie/doubleiter.go | 184 ++++++ versionmgr/merkletrie/internal/frame/frame.go | 92 +++ .../merkletrie/internal/frame/frame_test.go | 101 ++++ versionmgr/merkletrie/internal/fsnoder/dir.go | 148 +++++ .../merkletrie/internal/fsnoder/dir_test.go | 364 ++++++++++++ versionmgr/merkletrie/internal/fsnoder/doc.go | 52 ++ .../merkletrie/internal/fsnoder/file.go | 80 +++ .../merkletrie/internal/fsnoder/file_test.go | 67 +++ versionmgr/merkletrie/internal/fsnoder/new.go | 194 +++++++ .../merkletrie/internal/fsnoder/new_test.go | 332 +++++++++++ versionmgr/merkletrie/iter.go | 215 +++++++ versionmgr/merkletrie/iter_test.go | 474 ++++++++++++++++ versionmgr/merkletrie/noder/noder.go | 55 ++ versionmgr/merkletrie/noder/noder_test.go | 90 +++ versionmgr/merkletrie/noder/path.go | 98 ++++ versionmgr/merkletrie/noder/path_test.go | 165 ++++++ versionmgr/tree_node.go | 23 +- versionmgr/worktree.go | 143 ++--- versionmgr/worktree_test.go | 50 +- 51 files changed, 4947 insertions(+), 763 deletions(-) create mode 100644 models/commit.go create mode 100644 models/commit_test.go delete mode 100644 models/object.go create mode 100644 models/tag.go create mode 100644 models/tag_test.go create mode 100644 models/tree.go rename models/{object_test.go => tree_test.go} (62%) create mode 100644 versionmgr/merkletrie/README.md create mode 100644 versionmgr/merkletrie/change.go create mode 100644 versionmgr/merkletrie/change_test.go create mode 100644 versionmgr/merkletrie/difftree.go create mode 100644 versionmgr/merkletrie/difftree_test.go create mode 100644 versionmgr/merkletrie/doc.go create mode 100644 versionmgr/merkletrie/doubleiter.go create mode 100644 versionmgr/merkletrie/internal/frame/frame.go create mode 100644 versionmgr/merkletrie/internal/frame/frame_test.go create mode 100644 versionmgr/merkletrie/internal/fsnoder/dir.go create mode 100644 versionmgr/merkletrie/internal/fsnoder/dir_test.go create mode 100644 versionmgr/merkletrie/internal/fsnoder/doc.go create mode 100644 versionmgr/merkletrie/internal/fsnoder/file.go create mode 100644 versionmgr/merkletrie/internal/fsnoder/file_test.go create mode 100644 versionmgr/merkletrie/internal/fsnoder/new.go create mode 100644 versionmgr/merkletrie/internal/fsnoder/new_test.go create mode 100644 versionmgr/merkletrie/iter.go create mode 100644 versionmgr/merkletrie/iter_test.go create mode 100644 versionmgr/merkletrie/noder/noder.go create mode 100644 versionmgr/merkletrie/noder/noder_test.go create mode 100644 versionmgr/merkletrie/noder/path.go create mode 100644 versionmgr/merkletrie/noder/path_test.go diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index c3954069..da755952 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -74,7 +74,6 @@ type Commit struct { Message string `json:"Message"` ParentHashes []string `json:"ParentHashes"` TreeHash string `json:"TreeHash"` - Type int8 `json:"Type"` UpdatedAt time.Time `json:"UpdatedAt"` } @@ -190,9 +189,9 @@ type Signature struct { // TreeEntry defines model for TreeEntry. type TreeEntry struct { - Hash *string `json:"Hash,omitempty"` - Mode *uint32 `json:"Mode,omitempty"` - Name *string `json:"Name,omitempty"` + Hash *string `json:"Hash,omitempty"` + IsDir *bool `json:"IsDir,omitempty"` + Name *string `json:"Name,omitempty"` } // UpdateRepository defines model for UpdateRepository. @@ -5962,70 +5961,70 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index cd1e49f8..067ecea0 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -157,15 +157,23 @@ components: required: - Hash - Type + - CheckSum + - Properties - Size - CreatedAt - UpdatedAt properties: Hash: type: string + CheckSum: + type: string Type: type: integer format: int8 + Properties: + type: object + additionalProperties: + type: string Size: type: integer format: int64 @@ -208,9 +216,6 @@ components: properties: Hash: type: string - Type: - type: integer - format: int8 Author: $ref: "#/components/schemas/Signature" Committer: @@ -238,17 +243,27 @@ components: type: string Hash: type: string - Mode: - type: integer - format: uint32 + IsDir: + type: boolean TreeNode: type: object + required: + - Hash + - Type + - Properties + - SubObjects + - CreatedAt + - UpdatedAt properties: Hash: type: string Type: type: integer format: int8 + Properties: + type: object + additionalProperties: + type: string SubObjects: type: array items: diff --git a/controller/commit_ctl.go b/controller/commit_ctl.go index 624d9dd7..c764aad1 100644 --- a/controller/commit_ctl.go +++ b/controller/commit_ctl.go @@ -33,13 +33,13 @@ func (commitCtl CommitController) GetEntriesInCommit(ctx context.Context, w *api return } - commit, err := commitCtl.Repo.ObjectRepo().Get(ctx, models.NewGetObjParams().SetHash(ref.CommitHash)) + commit, err := commitCtl.Repo.CommitRepo().Commit(ctx, ref.CommitHash) if err != nil { w.Error(err) return } - workTree, err := versionmgr.NewWorkTree(ctx, commitCtl.Repo.ObjectRepo(), models.NewRootTreeEntry(commit.TreeHash)) + workTree, err := versionmgr.NewWorkTree(ctx, commitCtl.Repo.FileTreeRepo(), models.NewRootTreeEntry(commit.TreeHash)) if err != nil { w.Error(err) return @@ -75,7 +75,7 @@ func (commitCtl CommitController) GetCommitDiff(ctx context.Context, w *api.Jiao return } - bashCommit, err := commitCtl.Repo.ObjectRepo().Commit(ctx, bashCommitHash) + bashCommit, err := commitCtl.Repo.CommitRepo().Commit(ctx, bashCommitHash) if err != nil { w.Error(err) return diff --git a/controller/object_ctl.go b/controller/object_ctl.go index 2e524e4e..dafba3f5 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -61,13 +61,13 @@ func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsRes w.Error(err) return } - commit, err := oct.Repo.ObjectRepo().Commit(ctx, ref.CommitHash) + commit, err := oct.Repo.CommitRepo().Commit(ctx, ref.CommitHash) if err != nil { w.Error(err) return } - workTree, err := versionmgr.NewWorkTree(ctx, oct.Repo.ObjectRepo(), models.NewRootTreeEntry(commit.TreeHash)) + workTree, err := versionmgr.NewWorkTree(ctx, oct.Repo.FileTreeRepo(), models.NewRootTreeEntry(commit.TreeHash)) if err != nil { w.Error(err) return @@ -111,13 +111,13 @@ func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsRespon w.Error(err) return } - commit, err := oct.Repo.ObjectRepo().Commit(ctx, ref.CommitHash) + commit, err := oct.Repo.CommitRepo().Commit(ctx, ref.CommitHash) if err != nil { w.Error(err) return } - workTree, err := versionmgr.NewWorkTree(ctx, oct.Repo.ObjectRepo(), models.NewRootTreeEntry(commit.TreeHash)) + workTree, err := versionmgr.NewWorkTree(ctx, oct.Repo.FileTreeRepo(), models.NewRootTreeEntry(commit.TreeHash)) if err != nil { w.Error(err) return @@ -195,14 +195,14 @@ func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsRespo return } - commit, err := oct.Repo.ObjectRepo().Commit(ctx, ref.CommitHash) + commit, err := oct.Repo.CommitRepo().Commit(ctx, ref.CommitHash) if err != nil { w.Error(err) return } - objRepo := oct.Repo.ObjectRepo() - treeOp, err := versionmgr.NewWorkTree(ctx, oct.Repo.ObjectRepo(), models.NewRootTreeEntry(commit.TreeHash)) + fileRepo := oct.Repo.FileTreeRepo() + treeOp, err := versionmgr.NewWorkTree(ctx, fileRepo, models.NewRootTreeEntry(commit.TreeHash)) if err != nil { w.Error(err) return @@ -220,7 +220,7 @@ func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsRespo objectWithName := existNodes[len(existNodes)-1] - blob, err := objRepo.Blob(ctx, objectWithName.Node().Hash) + blob, err := fileRepo.Blob(ctx, objectWithName.Node().Hash) if err != nil { w.Error(err) return @@ -307,12 +307,12 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes return err } - workingTree, err := versionmgr.NewWorkTree(ctx, dRepo.ObjectRepo(), models.NewRootTreeEntry(stash.CurrentTree)) + workingTree, err := versionmgr.NewWorkTree(ctx, dRepo.FileTreeRepo(), models.NewRootTreeEntry(stash.CurrentTree)) if err != nil { return err } - blob, err := workingTree.WriteBlob(ctx, oct.BlockAdapter, reader, r.ContentLength, block.PutOpts{}) + blob, err := workingTree.WriteBlob(ctx, oct.BlockAdapter, reader, r.ContentLength, models.DefaultLeafProperty()) if err != nil { return err } diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index 5bf0ce23..22c4116a 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -186,14 +186,14 @@ func (repositoryCtl RepositoryController) GetCommitsInRepository(ctx context.Con return } - commit, err := repositoryCtl.Repo.ObjectRepo().Commit(ctx, ref.CommitHash) + commit, err := repositoryCtl.Repo.CommitRepo().Commit(ctx, ref.CommitHash) if err != nil { w.Error(err) return } var commits []api.Commit - iter := versionmgr.NewCommitPreorderIter(versionmgr.NewCommitNode(ctx, commit, repositoryCtl.Repo.ObjectRepo()), nil, nil) + iter := versionmgr.NewCommitPreorderIter(versionmgr.NewCommitNode(ctx, commit, repositoryCtl.Repo.CommitRepo()), nil, nil) for { commit, err := iter.Next() if err == nil { @@ -215,7 +215,6 @@ func (repositoryCtl RepositoryController) GetCommitsInRepository(ctx context.Con Message: modelCommit.Message, ParentHashes: hash.HexArrayOfHashes(modelCommit.ParentHashes...), TreeHash: modelCommit.TreeHash.Hex(), - Type: int8(modelCommit.Type), UpdatedAt: modelCommit.UpdatedAt, }) continue diff --git a/controller/wip_ctl.go b/controller/wip_ctl.go index 8b4d0a00..ab2cf0c0 100644 --- a/controller/wip_ctl.go +++ b/controller/wip_ctl.go @@ -46,7 +46,7 @@ func (wipCtl WipController) CreateWip(ctx context.Context, w *api.JiaozifsRespon return } - baseCommit, err := wipCtl.Repo.ObjectRepo().Commit(ctx, ref.CommitHash) + baseCommit, err := wipCtl.Repo.CommitRepo().Commit(ctx, ref.CommitHash) if err != nil { w.Error(err) return @@ -144,7 +144,7 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon return } - commit, err := wipCtl.Repo.ObjectRepo().Commit(ctx, ref.CommitHash) + commit, err := wipCtl.Repo.CommitRepo().Commit(ctx, ref.CommitHash) if err != nil { w.Error(err) return @@ -248,13 +248,13 @@ func (wipCtl WipController) GetWipChanges(ctx context.Context, w *api.JiaozifsRe return } - commit, err := wipCtl.Repo.ObjectRepo().Commit(ctx, wip.BaseCommit) + commit, err := wipCtl.Repo.CommitRepo().Commit(ctx, wip.BaseCommit) if err != nil { w.Error(err) return } - workTree, err := versionmgr.NewWorkTree(ctx, wipCtl.Repo.ObjectRepo(), models.NewRootTreeEntry(commit.TreeHash)) + workTree, err := versionmgr.NewWorkTree(ctx, wipCtl.Repo.FileTreeRepo(), models.NewRootTreeEntry(commit.TreeHash)) if err != nil { w.Error(err) return diff --git a/go.mod b/go.mod index 5e8a87e5..2dd771d8 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/flowchartsman/swaggerui v0.0.0-20221017034628-909ed4f3701b github.com/getkin/kin-openapi v0.118.0 github.com/go-chi/chi/v5 v5.0.10 + github.com/go-git/go-billy/v5 v5.5.0 github.com/go-git/go-git/v5 v5.10.1 github.com/go-openapi/swag v0.22.4 github.com/go-test/deep v1.1.0 @@ -50,7 +51,6 @@ require ( github.com/stretchr/testify v1.8.4 github.com/thanhpk/randstr v1.0.6 github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc - github.com/treeverse/lakefs v1.3.1 github.com/uptrace/bun v1.1.16 github.com/uptrace/bun/dialect/pgdialect v1.1.16 github.com/uptrace/bun/driver/pgdriver v1.1.16 @@ -60,6 +60,7 @@ require ( golang.org/x/crypto v0.16.0 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 golang.org/x/oauth2 v0.13.0 + golang.org/x/text v0.14.0 google.golang.org/api v0.147.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c gopkg.in/yaml.v2 v2.4.0 @@ -94,6 +95,7 @@ require ( github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/containerd/continuity v0.3.0 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/docker/cli v23.0.6+incompatible // indirect github.com/docker/docker v23.0.6+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect @@ -102,6 +104,7 @@ require ( github.com/fatih/color v1.15.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v5 v5.0.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -173,7 +176,6 @@ require ( golang.org/x/net v0.19.0 // indirect golang.org/x/sync v0.5.0 // indirect golang.org/x/sys v0.15.0 // indirect - golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.16.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/appengine v1.6.7 // indirect @@ -183,7 +185,6 @@ require ( google.golang.org/grpc v1.58.3 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect mellium.im/sasl v0.3.1 // indirect ) diff --git a/go.sum b/go.sum index 631697b2..819577ac 100644 --- a/go.sum +++ b/go.sum @@ -46,7 +46,6 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f cloud.google.com/go/storage v1.33.0 h1:PVrDOkIC8qQVa1P3SXGpQvfuJhN2LHOoyZvWs8D2X5M= cloud.google.com/go/storage v1.33.0/go.mod h1:Hhh/dogNRGca7IWv1RC2YqEn0c0G77ctA/OxflYkiD8= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0 h1:fb8kj/Dh4CSwgsOzHeZY4Xh68cFVbzXx+ONXGMY//4w= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0/go.mod h1:uReU2sSxZExRPBAg3qKzmAucSi51+SP1OhohieR821Q= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 h1:BMAjVKJM0U/CYF27gA0ZMmXGkOcvfFtD0oHVZ1TIPRI= @@ -61,7 +60,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg6 github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 h1:WpB/QDNLpMw72xHJc34BNNykqSOeEJDAWkhf0u12/Jk= github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/MadAppGang/httplog v1.3.0 h1:1XU54TO8kiqTeO+7oZLKAM3RP/cJ7SadzslRcKspVHo= github.com/MadAppGang/httplog v1.3.0/go.mod h1:gpYEdkjh/Cda6YxtDy4AB7KY+fR7mb3SqBZw74A5hJ4= @@ -139,6 +137,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -178,6 +177,7 @@ github.com/getkin/kin-openapi v0.118.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BH github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git/v5 v5.10.1 h1:tu8/D8i+TWxgKpzQ3Vc43e+kkhXqtsZCKI/egajKnxk= github.com/go-git/go-git/v5 v5.10.1/go.mod h1:uEuHjxkHap8kAl//V5F/nNWwqIYtP/402ddd05mp0wg= @@ -192,6 +192,7 @@ github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+ github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= @@ -377,6 +378,7 @@ github.com/oapi-codegen/nethttp-middleware v1.0.1 h1:ZWvwfnMU0eloHX1VEJmQscQm374 github.com/oapi-codegen/nethttp-middleware v1.0.1/go.mod h1:P7xtAvpoqNB+5obR9qRCeefH7YlXWSK3KgPs/9WB8tE= github.com/oapi-codegen/runtime v1.1.0 h1:rJpoNUawn5XTvekgfkvSZr0RqEnoYpFkyvrzfWeFKWM= github.com/oapi-codegen/runtime v1.1.0/go.mod h1:BeSfBkWWWnAnGdyS+S/GnlbmHKzf8/hwkvelJZDeKA8= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= @@ -461,8 +463,6 @@ github.com/thanhpk/randstr v1.0.6 h1:psAOktJFD4vV9NEVb3qkhRSMvYh4ORRaj1+w/hn4B+o github.com/thanhpk/randstr v1.0.6/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= -github.com/treeverse/lakefs v1.3.1 h1:oCxDq0ODIl/i/QBl9eeGIl/Rx7KCWlQEIm88IiS+JMg= -github.com/treeverse/lakefs v1.3.1/go.mod h1:YHY5WiQ8RX+m3QtVyZS/34NqEzmg2GyxuY88sUrrfRY= github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= @@ -855,8 +855,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/models/commit.go b/models/commit.go new file mode 100644 index 00000000..4e2e5ff9 --- /dev/null +++ b/models/commit.go @@ -0,0 +1,132 @@ +package models + +import ( + "context" + "time" + + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/uptrace/bun" +) + +// Signature is used to identify who and when created a commit or tag. +type Signature struct { + // Name represents a person name. It is an arbitrary string. + Name string `bun:"name"` + // Email is an email, but it cannot be assumed to be well-formed. + Email string `bun:"email"` + // When is the timestamp of the signature. + When time.Time `bun:"when"` +} + +type Commit struct { + bun.BaseModel `bun:"table:commits"` + Hash hash.Hash `bun:"hash,pk,type:bytea"` + //////********commit********//////// + // Author is the original author of the commit. + Author Signature `bun:"author,type:jsonb"` + // Committer is the one performing the commit, might be different from + // Author. + Committer Signature `bun:"committer,type:jsonb"` + // MergeTag is the embedded tag object when a merge commit is created by + // merging a signed tag. + MergeTag string `bun:"merge_tag"` //todo + // Message is the commit/tag message, contains arbitrary text. + Message string `bun:"message"` + // TreeHash is the hash of the root tree of the commit. + TreeHash hash.Hash `bun:"tree_hash,type:bytea,notnull"` + // ParentHashes are the hashes of the parent commits of the commit. + ParentHashes []hash.Hash `bun:"parent_hashes,type:bytea[]"` + + CreatedAt time.Time `bun:"created_at"` + UpdatedAt time.Time `bun:"updated_at"` +} + +func (commit *Commit) GetHash() (hash.Hash, error) { + hasher := hash.NewHasher(hash.Md5) + err := hasher.WriteInt8(int8(CommitObject)) + if err != nil { + return nil, err + } + err = hasher.WriteString(commit.Author.Name) + if err != nil { + return nil, err + } + + err = hasher.WriteString(commit.Author.Email) + if err != nil { + return nil, err + } + + err = hasher.WritInt64(commit.Author.When.Unix()) + if err != nil { + return nil, err + } + + err = hasher.WriteString(commit.Committer.Name) + if err != nil { + return nil, err + } + + err = hasher.WriteString(commit.Committer.Email) + if err != nil { + return nil, err + } + + err = hasher.WritInt64(commit.Committer.When.Unix()) + if err != nil { + return nil, err + } + + err = hasher.WriteString(commit.MergeTag) + if err != nil { + return nil, err + } + + err = hasher.WriteString(commit.Message) + if err != nil { + return nil, err + } + + _, err = hasher.Write(commit.TreeHash) + if err != nil { + return nil, err + } + + for _, h := range commit.ParentHashes { + _, err = hasher.Write(h) + if err != nil { + return nil, err + } + } + + return hasher.Md5.Sum(nil), nil +} + +func (commit *Commit) NumParents() int { + return len(commit.ParentHashes) +} + +type ICommitRepo interface { + Commit(ctx context.Context, hash hash.Hash) (*Commit, error) + Insert(ctx context.Context, commit *Commit) (*Commit, error) +} +type CommitRepo struct { + db bun.IDB +} + +func NewCommitRepo(db bun.IDB) ICommitRepo { + return &CommitRepo{db} +} + +func (cr CommitRepo) Commit(ctx context.Context, hash hash.Hash) (*Commit, error) { + commit := &Commit{} + return commit, cr.db.NewSelect().Model(commit).Where("hash = ?", hash).Scan(ctx) +} + +func (cr CommitRepo) Insert(ctx context.Context, commit *Commit) (*Commit, error) { + _, err := cr.db.NewInsert().Model(commit).Exec(ctx) + if err != nil { + return nil, err + } + return commit, nil +} diff --git a/models/commit_test.go b/models/commit_test.go new file mode 100644 index 00000000..994564d9 --- /dev/null +++ b/models/commit_test.go @@ -0,0 +1,29 @@ +package models_test + +import ( + "context" + "testing" + + "github.com/brianvoe/gofakeit/v6" + "github.com/google/go-cmp/cmp" + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/stretchr/testify/require" +) + +func TestCommitRepo(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + commitRepo := models.NewCommitRepo(db) + + commitModel := &models.Commit{} + require.NoError(t, gofakeit.Struct(commitModel)) + newCommitModel, err := commitRepo.Insert(ctx, commitModel) + require.NoError(t, err) + commitModel, err = commitRepo.Commit(ctx, commitModel.Hash) + require.NoError(t, err) + + require.True(t, cmp.Equal(commitModel, newCommitModel, dbTimeCmpOpt)) +} diff --git a/models/filemode/filemode.go b/models/filemode/filemode.go index 9840970d..9237cfbc 100644 --- a/models/filemode/filemode.go +++ b/models/filemode/filemode.go @@ -2,6 +2,7 @@ package filemode import ( "encoding/binary" + "encoding/json" "fmt" "os" "strconv" @@ -188,3 +189,21 @@ func (m FileMode) ToOSFileMode() (os.FileMode, error) { return os.FileMode(0), fmt.Errorf("malformed mode (%s)", m) } +func (m *FileMode) UnmarshalJSON(data []byte) error { //nolint + var s string + err := json.Unmarshal(data, &s) + if err != nil { + return err + } + + v, err := New(s) + if err != nil { + return err + } + *m = v + return nil +} + +func (m FileMode) MarshalJSON() ([]byte, error) { + return json.Marshal(m.String()) +} diff --git a/models/filemode/filemode_test.go b/models/filemode/filemode_test.go index 02bf5c5c..eeacac31 100644 --- a/models/filemode/filemode_test.go +++ b/models/filemode/filemode_test.go @@ -1,6 +1,7 @@ package filemode import ( + "encoding/json" "os" "testing" @@ -44,6 +45,20 @@ func (s *ModeSuite) TestNew(c *C) { } } +func (s *ModeSuite) TestJson(c *C) { + type A struct { + Mode FileMode + } + a := A{Mode: Regular} + aData, err := json.Marshal(a) + c.Assert(err, IsNil) + c.Assert(string(aData), Equals, `{"Mode":"0100644"}`) + a2 := A{} + err = json.Unmarshal(aData, &a2) + c.Assert(err, IsNil) + c.Assert(a2.Mode, Equals, Regular) +} + func (s *ModeSuite) TestNewErrors(c *C) { for _, input := range [...]string{ "0x81a4", // Regular in hex diff --git a/models/migrations/20210505110026_init_project.go b/models/migrations/20210505110026_init_project.go index 470a9ca5..3cd8c957 100644 --- a/models/migrations/20210505110026_init_project.go +++ b/models/migrations/20210505110026_init_project.go @@ -59,9 +59,23 @@ func init() { if err != nil { return err } - //object + //commits _, err = db.NewCreateTable(). - Model((*models.Object)(nil)). + Model((*models.Commit)(nil)). + Exec(ctx) + if err != nil { + return err + } + //tags + _, err = db.NewCreateTable(). + Model((*models.Tag)(nil)). + Exec(ctx) + if err != nil { + return err + } + //filetree + _, err = db.NewCreateTable(). + Model((*models.FileTree)(nil)). Exec(ctx) if err != nil { return err diff --git a/models/object.go b/models/object.go deleted file mode 100644 index ed513659..00000000 --- a/models/object.go +++ /dev/null @@ -1,437 +0,0 @@ -package models - -import ( - "bytes" - "context" - "time" - - "github.com/jiaozifs/jiaozifs/models/filemode" - "github.com/jiaozifs/jiaozifs/utils/hash" - "github.com/uptrace/bun" -) - -// ObjectType internal object type -// Integer values from 0 to 7 map to those exposed by git. -// AnyObject is used to represent any from 0 to 7. -type ObjectType int8 - -const ( - InvalidObject ObjectType = 0 - CommitObject ObjectType = 1 - TreeObject ObjectType = 2 - BlobObject ObjectType = 3 - TagObject ObjectType = 4 -) - -// Signature is used to identify who and when created a commit or tag. -type Signature struct { - // Name represents a person name. It is an arbitrary string. - Name string `bun:"name"` - // Email is an email, but it cannot be assumed to be well-formed. - Email string `bun:"email"` - // When is the timestamp of the signature. - When time.Time `bun:"when"` -} - -type TreeEntry struct { - Name string `bun:"name"` - Mode filemode.FileMode `bun:"mode"` - Hash hash.Hash `bun:"hash"` -} - -func NewRootTreeEntry(hash hash.Hash) TreeEntry { - return TreeEntry{ - Name: "", - Mode: filemode.Dir, - Hash: hash, - } -} -func (treeEntry TreeEntry) Equal(other TreeEntry) bool { - return bytes.Equal(treeEntry.Hash, other.Hash) && treeEntry.Mode == other.Mode && treeEntry.Name == other.Name -} - -type Blob struct { - bun.BaseModel `bun:"table:objects"` - Hash hash.Hash `bun:"hash,pk,type:bytea"` - Type ObjectType `bun:"type"` - Size int64 `bun:"size"` - - CreatedAt time.Time `bun:"created_at"` - UpdatedAt time.Time `bun:"updated_at"` -} - -func (blob *Blob) Object() *Object { - return &Object{ - Hash: blob.Hash, - Type: blob.Type, - Size: blob.Size, - CreatedAt: blob.CreatedAt, - UpdatedAt: blob.UpdatedAt, - } -} - -type TreeNode struct { - bun.BaseModel `bun:"table:objects"` - Hash hash.Hash `bun:"hash,pk,type:bytea"` - Type ObjectType `bun:"type"` - SubObjects []TreeEntry `bun:"subObjs,type:jsonb"` - - CreatedAt time.Time `bun:"created_at"` - UpdatedAt time.Time `bun:"updated_at"` -} - -func NewTreeNode(subObjects ...TreeEntry) (*TreeNode, error) { - newTree := &TreeNode{ - Type: TreeObject, - SubObjects: subObjects, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - hash, err := newTree.GetHash() - if err != nil { - return nil, err - } - newTree.Hash = hash - return newTree, nil -} - -func (tn *TreeNode) Object() *Object { - return &Object{ - Hash: tn.Hash, - Type: tn.Type, - SubObjects: tn.SubObjects, - CreatedAt: tn.CreatedAt, - UpdatedAt: tn.UpdatedAt, - } -} - -func (tn *TreeNode) GetHash() (hash.Hash, error) { - hasher := hash.NewHasher(hash.Md5) - err := hasher.WriteInt8(int8(tn.Type)) - if err != nil { - return nil, err - } - for _, obj := range tn.SubObjects { - _, err = hasher.Write(obj.Hash) - if err != nil { - return nil, err - } - err = hasher.WriteString(obj.Name) - if err != nil { - return nil, err - } - err = hasher.WriteUint32(uint32(obj.Mode)) - if err != nil { - return nil, err - } - } - - return hasher.Md5.Sum(nil), nil -} - -type Commit struct { - bun.BaseModel `bun:"table:objects"` - Hash hash.Hash `bun:"hash,pk,type:bytea"` - Type ObjectType `bun:"type"` - //////********commit********//////// - // Author is the original author of the commit. - Author Signature `bun:"author,type:jsonb"` - // Committer is the one performing the commit, might be different from - // Author. - Committer Signature `bun:"committer,type:jsonb"` - // MergeTag is the embedded tag object when a merge commit is created by - // merging a signed tag. - MergeTag string `bun:"merge_tag"` //todo - // Message is the commit/tag message, contains arbitrary text. - Message string `bun:"message"` - // TreeHash is the hash of the root tree of the commit. - TreeHash hash.Hash `bun:"tree_hash,type:bytea,notnull"` - // ParentHashes are the hashes of the parent commits of the commit. - ParentHashes []hash.Hash `bun:"parent_hashes,type:bytea[]"` - - CreatedAt time.Time `bun:"created_at"` - UpdatedAt time.Time `bun:"updated_at"` -} - -func (commit *Commit) GetHash() (hash.Hash, error) { - hasher := hash.NewHasher(hash.Md5) - err := hasher.WriteInt8(int8(commit.Type)) - if err != nil { - return nil, err - } - err = hasher.WriteString(commit.Author.Name) - if err != nil { - return nil, err - } - - err = hasher.WriteString(commit.Author.Email) - if err != nil { - return nil, err - } - - err = hasher.WritInt64(commit.Author.When.Unix()) - if err != nil { - return nil, err - } - - err = hasher.WriteString(commit.Committer.Name) - if err != nil { - return nil, err - } - - err = hasher.WriteString(commit.Committer.Email) - if err != nil { - return nil, err - } - - err = hasher.WritInt64(commit.Committer.When.Unix()) - if err != nil { - return nil, err - } - - err = hasher.WriteString(commit.MergeTag) - if err != nil { - return nil, err - } - - err = hasher.WriteString(commit.Message) - if err != nil { - return nil, err - } - - _, err = hasher.Write(commit.TreeHash) - if err != nil { - return nil, err - } - - for _, h := range commit.ParentHashes { - _, err = hasher.Write(h) - if err != nil { - return nil, err - } - } - - return hasher.Md5.Sum(nil), nil -} - -func (commit *Commit) NumParents() int { - return len(commit.ParentHashes) -} - -func (commit *Commit) Object() *Object { - return &Object{ - Hash: commit.Hash, - Type: commit.Type, - Author: commit.Author, - Committer: commit.Committer, - MergeTag: commit.MergeTag, - Message: commit.Message, - TreeHash: commit.TreeHash, - ParentHashes: commit.ParentHashes, - CreatedAt: commit.CreatedAt, - UpdatedAt: commit.UpdatedAt, - } -} - -type Tag struct { - bun.BaseModel `bun:"table:objects"` - Hash hash.Hash `bun:"hash,pk,type:bytea"` - Type ObjectType `bun:"type"` - //////********commit********//////// - // Name of the tag. - Name string `bun:"name"` - // Tagger is the one who created the tag. - Tagger Signature `bun:"tagger,type:jsonb"` - // TargetType is the object type of the target. - TargetType ObjectType `bun:"target_type"` - // Target is the hash of the target object. - Target hash.Hash `bun:"target,type:bytea"` - // Message is the tag message, contains arbitrary text. - Message string `bun:"message"` - - CreatedAt time.Time `bun:"created_at"` - UpdatedAt time.Time `bun:"updated_at"` -} - -func (tag *Tag) Object() *Object { - return &Object{ - Hash: tag.Hash, - Type: tag.Type, - Name: tag.Name, - Tagger: tag.Tagger, - TargetType: tag.TargetType, - Target: tag.Target, - Message: tag.Message, - CreatedAt: tag.CreatedAt, - UpdatedAt: tag.UpdatedAt, - } -} - -type Object struct { - bun.BaseModel `bun:"table:objects"` - Hash hash.Hash `bun:"hash,pk,type:bytea"` - Type ObjectType `bun:"type"` - Size int64 `bun:"size"` - //tree - SubObjects []TreeEntry `bun:"subObjs,type:jsonb"` - - //////********commit********//////// - // Author is the original author of the commit. - Author Signature `bun:"author,type:jsonb"` - // Committer is the one performing the commit, might be different from - // Author. - Committer Signature `bun:"committer,type:jsonb"` - // MergeTag is the embedded tag object when a merge commit is created by - // merging a signed tag. - MergeTag string `bun:"merge_tag"` //todo - // Message is the commit/tag message, contains arbitrary text. - Message string `bun:"message"` - // TreeHash is the hash of the root tree of the commit. - TreeHash hash.Hash `bun:"tree_hash,type:bytea"` - // ParentHashes are the hashes of the parent commits of the commit. - ParentHashes []hash.Hash `bun:"parent_hashes,type:bytea[]"` - - //////********commit********//////// - // Name of the tag. - Name string `bun:"name"` - // Tagger is the one who created the tag. - Tagger Signature `bun:"tagger,type:jsonb"` - // TargetType is the object type of the target. - TargetType ObjectType `bun:"target_type"` - // Target is the hash of the target object. - Target hash.Hash `bun:"target,type:bytea"` - - CreatedAt time.Time `bun:"created_at"` - UpdatedAt time.Time `bun:"updated_at"` -} - -func (obj *Object) Blob() *Blob { - return &Blob{ - Hash: obj.Hash, - Type: obj.Type, - Size: obj.Size, - CreatedAt: obj.CreatedAt, - UpdatedAt: obj.UpdatedAt, - } -} - -func (obj *Object) TreeNode() *TreeNode { - return &TreeNode{ - Hash: obj.Hash, - Type: obj.Type, - SubObjects: obj.SubObjects, - CreatedAt: obj.CreatedAt, - UpdatedAt: obj.UpdatedAt, - } -} - -func (obj *Object) Commit() *Commit { - return &Commit{ - Hash: obj.Hash, - Type: obj.Type, - Author: obj.Author, - Committer: obj.Committer, - MergeTag: obj.MergeTag, - Message: obj.Message, - TreeHash: obj.TreeHash, - ParentHashes: obj.ParentHashes, - CreatedAt: obj.CreatedAt, - UpdatedAt: obj.UpdatedAt, - } -} - -func (obj *Object) Tag() *Tag { - return &Tag{ - Hash: obj.Hash, - Type: obj.Type, - Name: obj.Name, - Tagger: obj.Tagger, - TargetType: obj.TargetType, - Target: obj.Target, - Message: obj.Message, - CreatedAt: obj.CreatedAt, - UpdatedAt: obj.UpdatedAt, - } -} - -type GetObjParams struct { - Hash hash.Hash -} - -func NewGetObjParams() *GetObjParams { - return &GetObjParams{} -} - -func (gop *GetObjParams) SetHash(hash hash.Hash) *GetObjParams { - gop.Hash = hash - return gop -} - -type IObjectRepo interface { - Insert(ctx context.Context, repo *Object) (*Object, error) - Get(ctx context.Context, params *GetObjParams) (*Object, error) - Count(ctx context.Context) (int, error) - List(ctx context.Context) ([]Object, error) - Blob(ctx context.Context, hash hash.Hash) (*Blob, error) - TreeNode(ctx context.Context, hash hash.Hash) (*TreeNode, error) - Commit(ctx context.Context, hash hash.Hash) (*Commit, error) - Tag(ctx context.Context, hash hash.Hash) (*Tag, error) -} - -var _ IObjectRepo = (*ObjectRepo)(nil) - -type ObjectRepo struct { - db bun.IDB -} - -func NewObjectRepo(db bun.IDB) IObjectRepo { - return &ObjectRepo{db: db} -} - -func (o ObjectRepo) Insert(ctx context.Context, obj *Object) (*Object, error) { - _, err := o.db.NewInsert().Model(obj).Ignore().Exec(ctx) - if err != nil { - return nil, err - } - return obj, nil -} - -func (o ObjectRepo) Get(ctx context.Context, params *GetObjParams) (*Object, error) { - repo := &Object{} - query := o.db.NewSelect().Model(repo) - - if params.Hash != nil { - query = query.Where("hash = ?", params.Hash) - } - - return repo, query.Limit(1).Scan(ctx, repo) -} - -func (o ObjectRepo) Blob(ctx context.Context, hash hash.Hash) (*Blob, error) { - blob := &Blob{} - return blob, o.db.NewSelect().Model(blob).Limit(1).Where("hash = ?", hash).Scan(ctx) -} - -func (o ObjectRepo) TreeNode(ctx context.Context, hash hash.Hash) (*TreeNode, error) { - tree := &TreeNode{} - return tree, o.db.NewSelect().Model(tree).Limit(1).Where("hash = ?", hash).Scan(ctx) -} - -func (o ObjectRepo) Commit(ctx context.Context, hash hash.Hash) (*Commit, error) { - commit := &Commit{} - return commit, o.db.NewSelect().Model(commit).Limit(1).Where("hash = ?", hash).Scan(ctx) -} - -func (o ObjectRepo) Tag(ctx context.Context, hash hash.Hash) (*Tag, error) { - tag := &Tag{} - return tag, o.db.NewSelect().Model(tag).Limit(1).Where("hash = ?", hash).Scan(ctx) -} - -func (o ObjectRepo) Count(ctx context.Context) (int, error) { - return o.db.NewSelect().Model((*Object)(nil)).Count(ctx) -} - -func (o ObjectRepo) List(ctx context.Context) ([]Object, error) { - var obj []Object - return obj, o.db.NewSelect().Model(&obj).Scan(ctx) -} diff --git a/models/repo.go b/models/repo.go index 894bff59..c708fcfb 100644 --- a/models/repo.go +++ b/models/repo.go @@ -18,7 +18,9 @@ func IsolationLevelOption(level sql.IsolationLevel) TxOption { type IRepo interface { Transaction(ctx context.Context, fn func(repo IRepo) error, opts ...TxOption) error UserRepo() IUserRepo - ObjectRepo() IObjectRepo + FileTreeRepo() IFileTreeRepo + CommitRepo() ICommitRepo + TagRepo() ITagRepo RefRepo() IRefRepo RepositoryRepo() IRepositoryRepo WipRepo() IWipRepo @@ -48,8 +50,16 @@ func (repo *PgRepo) UserRepo() IUserRepo { return NewUserRepo(repo.db) } -func (repo *PgRepo) ObjectRepo() IObjectRepo { - return NewObjectRepo(repo.db) +func (repo *PgRepo) FileTreeRepo() IFileTreeRepo { + return NewFileTree(repo.db) +} + +func (repo *PgRepo) CommitRepo() ICommitRepo { + return NewCommitRepo(repo.db) +} + +func (repo *PgRepo) TagRepo() ITagRepo { + return NewTagRepo(repo.db) } func (repo *PgRepo) RefRepo() IRefRepo { diff --git a/models/repo_test.go b/models/repo_test.go index 2f334dfe..bf05c729 100644 --- a/models/repo_test.go +++ b/models/repo_test.go @@ -35,9 +35,9 @@ func TestRepoTransaction(t *testing.T) { t.Run("transaction", func(t *testing.T) { pgRepo := models.NewRepo(db) err := pgRepo.Transaction(ctx, func(repo models.IRepo) error { - object := &models.Object{} + object := &models.FileTree{} require.NoError(t, gofakeit.Struct(object)) - _, err := repo.ObjectRepo().Insert(ctx, object) + _, err := repo.FileTreeRepo().Insert(ctx, object) require.NoError(t, err) return err }) diff --git a/models/tag.go b/models/tag.go new file mode 100644 index 00000000..38f8fe21 --- /dev/null +++ b/models/tag.go @@ -0,0 +1,64 @@ +package models + +import ( + "context" + "time" + + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/uptrace/bun" +) + +type Tag struct { + bun.BaseModel `bun:"table:tags"` + Hash hash.Hash `bun:"hash,pk,type:bytea"` + Type ObjectType `bun:"type"` + //////********commit********//////// + // Name of the tag. + Name string `bun:"name"` + // Tagger is the one who created the tag. + Tagger Signature `bun:"tagger,type:jsonb"` + // TargetType is the object type of the target. + TargetType ObjectType `bun:"target_type"` + // Target is the hash of the target object. + Target hash.Hash `bun:"target,type:bytea"` + // Message is the tag message, contains arbitrary text. + Message string `bun:"message"` + + CreatedAt time.Time `bun:"created_at"` + UpdatedAt time.Time `bun:"updated_at"` +} + +type ITagRepo interface { + Insert(ctx context.Context, tag *Tag) (*Tag, error) + Tag(ctx context.Context, hash hash.Hash) (*Tag, error) +} + +type TagRepo struct { + db bun.IDB +} + +func NewTagRepo(db bun.IDB) ITagRepo { + return &TagRepo{db: db} +} + +func (t *TagRepo) Insert(ctx context.Context, tag *Tag) (*Tag, error) { + _, err := t.db.NewInsert(). + Model(tag). + Exec(ctx) + if err != nil { + return nil, err + } + return tag, nil +} + +func (t *TagRepo) Tag(ctx context.Context, hash hash.Hash) (*Tag, error) { + tag := &Tag{} + err := t.db.NewSelect(). + Model(tag). + Where("hash = ?", hash). + Scan(ctx) + if err != nil { + return nil, err + } + return tag, nil +} diff --git a/models/tag_test.go b/models/tag_test.go new file mode 100644 index 00000000..83f605f3 --- /dev/null +++ b/models/tag_test.go @@ -0,0 +1,31 @@ +package models_test + +import ( + "context" + "testing" + + "github.com/jiaozifs/jiaozifs/models" + + "github.com/brianvoe/gofakeit/v6" + "github.com/google/go-cmp/cmp" + + "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/stretchr/testify/require" +) + +func TestTagRepo(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + tagRepo := models.NewTagRepo(db) + + tagModel := &models.Tag{} + require.NoError(t, gofakeit.Struct(tagModel)) + newTagModel, err := tagRepo.Insert(ctx, tagModel) + require.NoError(t, err) + tagModel, err = tagRepo.Tag(ctx, tagModel.Hash) + require.NoError(t, err) + + require.True(t, cmp.Equal(tagModel, newTagModel, dbTimeCmpOpt)) +} diff --git a/models/tree.go b/models/tree.go new file mode 100644 index 00000000..df2a3854 --- /dev/null +++ b/models/tree.go @@ -0,0 +1,313 @@ +package models + +import ( + "bytes" + "context" + "sort" + "time" + + "github.com/jiaozifs/jiaozifs/models/filemode" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/uptrace/bun" +) + +// ObjectType internal object type +// Integer values from 0 to 7 map to those exposed by git. +// AnyObject is used to represent any from 0 to 7. +type ObjectType int8 + +const ( + InvalidObject ObjectType = 0 + CommitObject ObjectType = 1 + TreeObject ObjectType = 2 + BlobObject ObjectType = 3 + TagObject ObjectType = 4 +) + +type TreeEntry struct { + Name string `bun:"name"` + IsDir bool `bun:"is_dir"` + Hash hash.Hash `bun:"hash"` +} + +func SortSubObjects(subObjects []TreeEntry) []TreeEntry { + sort.Slice(subObjects, func(i, j int) bool { + return subObjects[i].Name < subObjects[j].Name + }) + return subObjects +} + +func NewRootTreeEntry(hash hash.Hash) TreeEntry { + return TreeEntry{ + Name: "", + Hash: hash, + } +} +func (treeEntry TreeEntry) Equal(other TreeEntry) bool { + return bytes.Equal(treeEntry.Hash, other.Hash) && treeEntry.Name == other.Name +} + +type Property struct { + Mode filemode.FileMode `json:"mode"` +} + +func DefaultDirProperty() Property { + return Property{ + Mode: filemode.Dir, + } +} + +func DefaultLeafProperty() Property { + return Property{ + Mode: filemode.Regular, + } +} + +func (props Property) ToMap() map[string]string { + return map[string]string{ + "mode": props.Mode.String(), + } +} + +type Blob struct { + bun.BaseModel `bun:"table:trees"` + Hash hash.Hash `bun:"hash,pk,type:bytea"` + CheckSum hash.Hash `bun:"check_sum,type:bytea"` + Type ObjectType `bun:"type"` + Size int64 `bun:"size"` + Properties Property `bun:"properties,type:jsonb"` + + CreatedAt time.Time `bun:"created_at"` + UpdatedAt time.Time `bun:"updated_at"` +} + +func NewBlob(props Property, checkSum hash.Hash, size int64) (*Blob, error) { + blob := &Blob{ + CheckSum: checkSum, + Type: BlobObject, + Size: size, + Properties: props, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + hash, err := blob.calculateHash() + if err != nil { + return nil, err + } + blob.Hash = hash + return blob, err +} + +func (blob *Blob) calculateHash() (hash.Hash, error) { + hasher := hash.NewHasher(hash.Md5) + err := hasher.WriteInt8(int8(blob.Type)) + if err != nil { + return nil, err + } + + _, err = hasher.Write(blob.CheckSum) + if err != nil { + return nil, err + } + + //write mode property todo change reflect + for k, v := range blob.Properties.ToMap() { + err = hasher.WriteString(k) + if err != nil { + return nil, err + } + + err = hasher.WriteString(v) + if err != nil { + return nil, err + } + } + return hasher.Md5.Sum(nil), nil +} + +func (blob *Blob) FileTree() *FileTree { + return &FileTree{ + Hash: blob.Hash, + Type: blob.Type, + Size: blob.Size, + Properties: blob.Properties, + CreatedAt: blob.CreatedAt, + UpdatedAt: blob.UpdatedAt, + } +} + +type TreeNode struct { + bun.BaseModel `bun:"table:trees"` + Hash hash.Hash `bun:"hash,pk,type:bytea"` + Type ObjectType `bun:"type"` + SubObjects []TreeEntry `bun:"subObjs,type:jsonb"` + Properties Property `bun:"properties,type:jsonb"` + + CreatedAt time.Time `bun:"created_at"` + UpdatedAt time.Time `bun:"updated_at"` +} + +func NewTreeNode(props Property, subObjects ...TreeEntry) (*TreeNode, error) { + newTree := &TreeNode{ + Type: TreeObject, + SubObjects: SortSubObjects(subObjects), + Properties: props, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + hash, err := newTree.calculateHash() + if err != nil { + return nil, err + } + newTree.Hash = hash + return newTree, nil +} + +func (tn *TreeNode) FileTree() *FileTree { + return &FileTree{ + Hash: tn.Hash, + Type: tn.Type, + SubObjects: tn.SubObjects, + Properties: tn.Properties, + CreatedAt: tn.CreatedAt, + UpdatedAt: tn.UpdatedAt, + } +} + +func (tn *TreeNode) calculateHash() (hash.Hash, error) { + hasher := hash.NewHasher(hash.Md5) + err := hasher.WriteInt8(int8(tn.Type)) + if err != nil { + return nil, err + } + for _, obj := range tn.SubObjects { + _, err = hasher.Write(obj.Hash) + if err != nil { + return nil, err + } + err = hasher.WriteString(obj.Name) + if err != nil { + return nil, err + } + } + + for name, value := range tn.Properties.ToMap() { + err = hasher.WriteString(name) + if err != nil { + return nil, err + } + err = hasher.WriteString(value) + if err != nil { + return nil, err + } + } + + return hasher.Md5.Sum(nil), nil +} + +type FileTree struct { + bun.BaseModel `bun:"table:trees"` + Hash hash.Hash `bun:"hash,pk,type:bytea"` + Type ObjectType `bun:"type"` + Size int64 `bun:"size"` + CheckSum hash.Hash `bun:"check_sum,type:bytea"` + Properties Property `bun:"properties,type:jsonb"` + //tree + SubObjects []TreeEntry `bun:"subObjs,type:jsonb"` + + CreatedAt time.Time `bun:"created_at"` + UpdatedAt time.Time `bun:"updated_at"` +} + +func (fileTree *FileTree) Blob() *Blob { + return &Blob{ + Hash: fileTree.Hash, + Type: fileTree.Type, + Size: fileTree.Size, + Properties: fileTree.Properties, + CheckSum: fileTree.CheckSum, + CreatedAt: fileTree.CreatedAt, + UpdatedAt: fileTree.UpdatedAt, + } +} + +func (fileTree *FileTree) TreeNode() *TreeNode { + return &TreeNode{ + Hash: fileTree.Hash, + Type: fileTree.Type, + Properties: fileTree.Properties, + SubObjects: fileTree.SubObjects, + CreatedAt: fileTree.CreatedAt, + UpdatedAt: fileTree.UpdatedAt, + } +} + +type GetObjParams struct { + Hash hash.Hash +} + +func NewGetObjParams() *GetObjParams { + return &GetObjParams{} +} + +func (gop *GetObjParams) SetHash(hash hash.Hash) *GetObjParams { + gop.Hash = hash + return gop +} + +type IFileTreeRepo interface { + Insert(ctx context.Context, repo *FileTree) (*FileTree, error) + Get(ctx context.Context, params *GetObjParams) (*FileTree, error) + Count(ctx context.Context) (int, error) + List(ctx context.Context) ([]FileTree, error) + Blob(ctx context.Context, hash hash.Hash) (*Blob, error) + TreeNode(ctx context.Context, hash hash.Hash) (*TreeNode, error) +} + +var _ IFileTreeRepo = (*FileTreeRepo)(nil) + +type FileTreeRepo struct { + db bun.IDB +} + +func NewFileTree(db bun.IDB) IFileTreeRepo { + return &FileTreeRepo{db: db} +} + +func (o FileTreeRepo) Insert(ctx context.Context, obj *FileTree) (*FileTree, error) { + _, err := o.db.NewInsert().Model(obj).Ignore().Exec(ctx) + if err != nil { + return nil, err + } + return obj, nil +} + +func (o FileTreeRepo) Get(ctx context.Context, params *GetObjParams) (*FileTree, error) { + repo := &FileTree{} + query := o.db.NewSelect().Model(repo) + + if params.Hash != nil { + query = query.Where("hash = ?", params.Hash) + } + + return repo, query.Limit(1).Scan(ctx, repo) +} + +func (o FileTreeRepo) Blob(ctx context.Context, hash hash.Hash) (*Blob, error) { + blob := &Blob{} + return blob, o.db.NewSelect().Model(blob).Limit(1).Where("hash = ?", hash).Scan(ctx) +} + +func (o FileTreeRepo) TreeNode(ctx context.Context, hash hash.Hash) (*TreeNode, error) { + tree := &TreeNode{} + return tree, o.db.NewSelect().Model(tree).Limit(1).Where("hash = ?", hash).Scan(ctx) +} + +func (o FileTreeRepo) Count(ctx context.Context) (int, error) { + return o.db.NewSelect().Model((*FileTree)(nil)).Count(ctx) +} + +func (o FileTreeRepo) List(ctx context.Context) ([]FileTree, error) { + var obj []FileTree + return obj, o.db.NewSelect().Model(&obj).Scan(ctx) +} diff --git a/models/object_test.go b/models/tree_test.go similarity index 62% rename from models/object_test.go rename to models/tree_test.go index f8f016fa..e0c3846f 100644 --- a/models/object_test.go +++ b/models/tree_test.go @@ -4,6 +4,8 @@ import ( "context" "testing" + "github.com/jiaozifs/jiaozifs/models/filemode" + "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" "github.com/jiaozifs/jiaozifs/models" @@ -11,15 +13,38 @@ import ( "github.com/stretchr/testify/require" ) +func Test_sortSubObjects(t *testing.T) { + entries := []models.TreeEntry{ + { + Name: "c.txt", + Hash: nil, + }, + { + Name: "a.txt", + Hash: nil, + }, + { + Name: "b.txt", + Hash: nil, + }, + } + + models.SortSubObjects(entries) + require.Equal(t, "a.txt", entries[0].Name) + require.Equal(t, "b.txt", entries[1].Name) + require.Equal(t, "c.txt", entries[2].Name) +} + func TestObjectRepo_Insert(t *testing.T) { ctx := context.Background() postgres, _, db := testhelper.SetupDatabase(ctx, t) defer postgres.Stop() //nolint - repo := models.NewObjectRepo(db) + repo := models.NewFileTree(db) - objModel := &models.Object{} + objModel := &models.FileTree{} require.NoError(t, gofakeit.Struct(objModel)) + objModel.Properties.Mode = filemode.Regular newObj, err := repo.Insert(ctx, objModel) require.NoError(t, err) require.NotEqual(t, nil, newObj.Hash) diff --git a/versionmgr/changes.go b/versionmgr/changes.go index 35d526b5..446a4f11 100644 --- a/versionmgr/changes.go +++ b/versionmgr/changes.go @@ -8,8 +8,8 @@ import ( "sort" "strings" - "github.com/go-git/go-git/v5/utils/merkletrie" - "github.com/go-git/go-git/v5/utils/merkletrie/noder" + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" ) var ( diff --git a/versionmgr/changes_test.go b/versionmgr/changes_test.go index d26e4b60..5fd69122 100644 --- a/versionmgr/changes_test.go +++ b/versionmgr/changes_test.go @@ -1,6 +1,7 @@ package versionmgr import ( + "bytes" "fmt" "path/filepath" "strconv" @@ -9,11 +10,11 @@ import ( "github.com/stretchr/testify/require" - "github.com/go-git/go-git/v5/utils/merkletrie" + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" "github.com/jiaozifs/jiaozifs/utils/hash" - "github.com/go-git/go-git/v5/utils/merkletrie/noder" + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" ) var _ noder.Noder = (*MockNode)(nil) @@ -31,6 +32,10 @@ func (m MockNode) Hash() []byte { return m.hash } +func (m MockNode) Equal(other noder.Noder) bool { + return bytes.Equal(m.Hash(), other.Hash()) +} + func (m MockNode) String() string { //TODO implement me panic("implement me") diff --git a/versionmgr/commit.go b/versionmgr/commit.go index f318c81f..1935ed97 100644 --- a/versionmgr/commit.go +++ b/versionmgr/commit.go @@ -19,18 +19,20 @@ var ( type CommitOp struct { commit *models.Commit - user models.IUserRepo - object models.IObjectRepo - wip models.IWipRepo + userRepo models.IUserRepo + fileTreeRepo models.IFileTreeRepo + commitRepo models.ICommitRepo + wipRepo models.IWipRepo } // NewCommitOp create commit operation with repo and exit commit, if operate with new repo, set commit arguments to nil func NewCommitOp(repo models.IRepo, commit *models.Commit) *CommitOp { return &CommitOp{ - commit: commit, - user: repo.UserRepo(), - object: repo.ObjectRepo(), - wip: repo.WipRepo(), + commit: commit, + userRepo: repo.UserRepo(), + fileTreeRepo: repo.FileTreeRepo(), + commitRepo: repo.CommitRepo(), + wipRepo: repo.WipRepo(), } } @@ -42,12 +44,12 @@ func (commitOp *CommitOp) Commit() *models.Commit { // AddCommit append a new commit to current head, read changes from wip, than create a new commit with parent point to current head, // and replace tree hash with wip's currentTreeHash. func (commitOp *CommitOp) AddCommit(ctx context.Context, committer *models.User, wipID uuid.UUID, msg string) (*CommitOp, error) { - wip, err := commitOp.wip.Get(ctx, models.NewGetWipParams().SetID(wipID)) + wip, err := commitOp.wipRepo.Get(ctx, models.NewGetWipParams().SetID(wipID)) if err != nil { return nil, err } - creator, err := commitOp.user.Get(ctx, models.NewGetUserParams().SetID(wip.CreatorID)) + creator, err := commitOp.userRepo.Get(ctx, models.NewGetUserParams().SetID(wip.CreatorID)) if err != nil { return nil, err } @@ -58,7 +60,6 @@ func (commitOp *CommitOp) AddCommit(ctx context.Context, committer *models.User, } commit := &models.Commit{ Hash: nil, - Type: models.CommitObject, Author: models.Signature{ Name: creator.Name, Email: creator.Email, @@ -81,26 +82,27 @@ func (commitOp *CommitOp) AddCommit(ctx context.Context, committer *models.User, return nil, err } commit.Hash = commitHash - _, err = commitOp.object.Insert(ctx, commit.Object()) + _, err = commitOp.commitRepo.Insert(ctx, commit) if err != nil { return nil, err } return &CommitOp{ - commit: commit, - user: commitOp.user, - object: commitOp.object, - wip: commitOp.wip, + commit: commit, + userRepo: commitOp.userRepo, + fileTreeRepo: commitOp.fileTreeRepo, + commitRepo: commitOp.commitRepo, + wipRepo: commitOp.wipRepo, }, nil } // DiffCommit find file changes in two commit func (commitOp *CommitOp) DiffCommit(ctx context.Context, toCommitID hash.Hash) (*Changes, error) { - workTree, err := NewWorkTree(ctx, commitOp.object, models.NewRootTreeEntry(commitOp.Commit().TreeHash)) + workTree, err := NewWorkTree(ctx, commitOp.fileTreeRepo, models.NewRootTreeEntry(commitOp.Commit().TreeHash)) if err != nil { return nil, err } - toCommit, err := commitOp.object.Commit(ctx, toCommitID) + toCommit, err := commitOp.commitRepo.Commit(ctx, toCommitID) if err != nil { return nil, err } @@ -111,14 +113,14 @@ func (commitOp *CommitOp) DiffCommit(ctx context.Context, toCommitID hash.Hash) // Merge implement merge like git, docs https://en.wikipedia.org/wiki/Merge_(version_control) func (commitOp *CommitOp) Merge(ctx context.Context, merger *models.User, toMergeCommitHash hash.Hash, msg string, resolver ConflictResolver) (*models.Commit, error) { - toMergeCommit, err := commitOp.object.Commit(ctx, toMergeCommitHash) + toMergeCommit, err := commitOp.commitRepo.Commit(ctx, toMergeCommitHash) if err != nil { return nil, err } //find accesstor - baseCommitNode := NewCommitNode(ctx, commitOp.Commit(), commitOp.object) - toMergeCommitNode := NewCommitNode(ctx, toMergeCommit, commitOp.object) + baseCommitNode := NewCommitNode(ctx, commitOp.Commit(), commitOp.commitRepo) + toMergeCommitNode := NewCommitNode(ctx, toMergeCommit, commitOp.commitRepo) { //do nothing while merge is ancestor of base @@ -160,24 +162,26 @@ func (commitOp *CommitOp) Merge(ctx context.Context, merger *models.User, toMerg if len(bestAncestor) > 1 { //merge cross merge create virtual commit firstCommit := &CommitOp{ - commit: bestAncestor[0].Commit(), - user: commitOp.user, - object: commitOp.object, - wip: commitOp.wip, + commit: bestAncestor[0].Commit(), + userRepo: commitOp.userRepo, + fileTreeRepo: commitOp.fileTreeRepo, + commitRepo: commitOp.commitRepo, + wipRepo: commitOp.wipRepo, } virtualCommit, err := firstCommit.Merge(ctx, merger, bestAncestor[1].Commit().Hash, "", resolver) if err != nil { return nil, err } - bestCommit = NewCommitNode(ctx, virtualCommit, commitOp.object) + bestCommit = NewCommitNode(ctx, virtualCommit, commitOp.commitRepo) } bestCommitOp := &CommitOp{ - commit: bestAncestor[0].Commit(), - user: commitOp.user, - object: commitOp.object, - wip: commitOp.wip, + commit: bestAncestor[0].Commit(), + userRepo: commitOp.userRepo, + fileTreeRepo: commitOp.fileTreeRepo, + commitRepo: commitOp.commitRepo, + wipRepo: commitOp.wipRepo, } baseDiff, err := bestCommitOp.DiffCommit(ctx, commitOp.Commit().Hash) @@ -191,7 +195,7 @@ func (commitOp *CommitOp) Merge(ctx context.Context, merger *models.User, toMerg } //merge diff - workTree, err := NewWorkTree(ctx, commitOp.object, models.NewRootTreeEntry(bestCommit.Commit().TreeHash)) + workTree, err := NewWorkTree(ctx, commitOp.fileTreeRepo, models.NewRootTreeEntry(bestCommit.Commit().TreeHash)) if err != nil { return nil, err } @@ -216,7 +220,6 @@ func (commitOp *CommitOp) Merge(ctx context.Context, merger *models.User, toMerg } mergeCommit := &models.Commit{ - Type: models.CommitObject, Author: author, Committer: author, MergeTag: "", @@ -232,9 +235,9 @@ func (commitOp *CommitOp) Merge(ctx context.Context, merger *models.User, toMerg } mergeCommit.Hash = hash - mergeCommitObject, err := commitOp.object.Insert(ctx, mergeCommit.Object()) + mergeCommitResult, err := commitOp.commitRepo.Insert(ctx, mergeCommit) if err != nil { return nil, err } - return mergeCommitObject.Commit(), nil + return mergeCommitResult, nil } diff --git a/versionmgr/commit_node.go b/versionmgr/commit_node.go index d48377b1..48b242f9 100644 --- a/versionmgr/commit_node.go +++ b/versionmgr/commit_node.go @@ -8,8 +8,6 @@ import ( "github.com/go-git/go-git/v5/plumbing/storer" "github.com/jiaozifs/jiaozifs/utils/hash" - "github.com/jiaozifs/jiaozifs/models/filemode" - "github.com/jiaozifs/jiaozifs/models" ) @@ -18,13 +16,13 @@ var ( ) type CommitNode struct { - ctx context.Context - commit *models.Commit - object models.IObjectRepo + ctx context.Context + commit *models.Commit + commitRepo models.ICommitRepo } -func NewCommitNode(ctx context.Context, commit *models.Commit, object models.IObjectRepo) *CommitNode { - return &CommitNode{ctx: ctx, commit: commit, object: object} +func NewCommitNode(ctx context.Context, commit *models.Commit, commitRepo models.ICommitRepo) *CommitNode { + return &CommitNode{ctx: ctx, commit: commit, commitRepo: commitRepo} } func (c *CommitNode) Ctx() context.Context { @@ -35,63 +33,51 @@ func (c *CommitNode) Commit() *models.Commit { return c.commit } -func (c *CommitNode) Object() models.IObjectRepo { - return c.object -} - -// Tree returns the Tree from the commit. -func (c *CommitNode) Tree() (*TreeNode, error) { - treeNode, err := c.object.TreeNode(c.ctx, c.commit.TreeHash) - if err != nil { - return nil, err - } - return NewTreeNode(c.ctx, models.TreeEntry{ - Name: "", - Mode: filemode.Dir, - Hash: treeNode.Hash, - }, c.object) +// TreeHash returns the TreeHash in the commit. +func (c *CommitNode) TreeHash() hash.Hash { + return c.commit.TreeHash } // Parents return a CommitIter to the parent Commits. func (c *CommitNode) Parents() ([]*CommitNode, error) { parentNodes := make([]*CommitNode, len(c.commit.ParentHashes)) for _, hash := range c.commit.ParentHashes { - commit, err := c.object.Commit(c.ctx, hash) + commit, err := c.commitRepo.Commit(c.ctx, hash) if err != nil { return nil, err } parentNodes = append(parentNodes, &CommitNode{ - ctx: c.ctx, - commit: commit, - object: c.object, + ctx: c.ctx, + commit: commit, + commitRepo: c.commitRepo, }) } return parentNodes, nil } func (c *CommitNode) GetCommit(hash hash.Hash) (*CommitNode, error) { - commit, err := c.object.Commit(c.ctx, hash) + commit, err := c.commitRepo.Commit(c.ctx, hash) if err != nil { return nil, err } return &CommitNode{ - ctx: c.ctx, - commit: commit, - object: c.object, + ctx: c.ctx, + commit: commit, + commitRepo: c.commitRepo, }, nil } func (c *CommitNode) GetCommits(hashes []hash.Hash) ([]*CommitNode, error) { commits := make([]*CommitNode, len(hashes)) for i, hash := range hashes { - commit, err := c.object.Commit(c.ctx, hash) + commit, err := c.commitRepo.Commit(c.ctx, hash) if err != nil { return nil, err } commits[i] = &CommitNode{ - ctx: c.ctx, - commit: commit, - object: c.object, + ctx: c.ctx, + commit: commit, + commitRepo: c.commitRepo, } } return commits, nil diff --git a/versionmgr/commit_test.go b/versionmgr/commit_test.go index c81530b2..fb6b091c 100644 --- a/versionmgr/commit_test.go +++ b/versionmgr/commit_test.go @@ -6,9 +6,11 @@ import ( "testing" "time" + "github.com/jiaozifs/jiaozifs/models/filemode" + "github.com/jiaozifs/jiaozifs/utils" - "github.com/go-git/go-git/v5/utils/merkletrie" + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" "github.com/google/uuid" @@ -46,7 +48,7 @@ func TestCommitOpDiffCommit(t *testing.T) { 1|b/e.txt |e1 ` - root1, err := makeRoot(ctx, repo.ObjectRepo(), EmptyDirEntry, testData1) + root1, err := makeRoot(ctx, repo.FileTreeRepo(), EmptyDirEntry, testData1) require.NoError(t, err) baseWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseRef.ID, EmptyRoot.Hash, root1.Hash) require.NoError(t, err) @@ -60,7 +62,7 @@ func TestCommitOpDiffCommit(t *testing.T) { 3|b/e.txt |e2 1|b/g.txt |g1 ` - root2, err := makeRoot(ctx, repo.ObjectRepo(), models.NewRootTreeEntry(root1.Hash), testData2) + root2, err := makeRoot(ctx, repo.FileTreeRepo(), models.NewRootTreeEntry(root1.Hash), testData2) require.NoError(t, err) secondWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseRef.ID, EmptyRoot.Hash, root2.Hash) @@ -120,7 +122,7 @@ func TestCommitOpMerge(t *testing.T) { 1|a.txt |h1 1|b/c.txt |h2 ` - oriRoot, err := makeRoot(ctx, repo.ObjectRepo(), EmptyDirEntry, testData) + oriRoot, err := makeRoot(ctx, repo.FileTreeRepo(), EmptyDirEntry, testData) require.NoError(t, err) //base branch baseRef, err := makeRef(ctx, repo.RefRepo(), "feat/base", project.ID, hash.Hash("a")) @@ -138,7 +140,7 @@ func TestCommitOpMerge(t *testing.T) { 3|a.txt |h5 3|b/c.txt |h2 ` - baseModify, err := makeRoot(ctx, repo.ObjectRepo(), models.NewRootTreeEntry(oriRoot.Hash), testData) + baseModify, err := makeRoot(ctx, repo.FileTreeRepo(), models.NewRootTreeEntry(oriRoot.Hash), testData) require.NoError(t, err) baseWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseRef.ID, oriRoot.Hash, baseModify.Hash) @@ -157,7 +159,7 @@ func TestCommitOpMerge(t *testing.T) { 3|a.txt |h4 3|b/c.txt |h2 ` - mergeModify, err := makeRoot(ctx, repo.ObjectRepo(), models.NewRootTreeEntry(oriRoot.Hash), testData) + mergeModify, err := makeRoot(ctx, repo.FileTreeRepo(), models.NewRootTreeEntry(oriRoot.Hash), testData) require.NoError(t, err) mergeWip, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeRef.ID, oriRoot.Hash, mergeModify.Hash) require.NoError(t, err) @@ -172,7 +174,7 @@ func TestCommitOpMerge(t *testing.T) { testData = ` 1|x.txt |h4 ` - rootF, err := makeRoot(ctx, repo.ObjectRepo(), models.NewRootTreeEntry(commitAB.TreeHash), testData) + rootF, err := makeRoot(ctx, repo.FileTreeRepo(), models.NewRootTreeEntry(commitAB.TreeHash), testData) require.NoError(t, err) mergeWipF, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeRef.ID, commitAB.TreeHash, rootF.Hash) require.NoError(t, err) @@ -189,7 +191,7 @@ func TestCommitOpMerge(t *testing.T) { 3|b/c.txt |h6 1|g/c.txt |h7 ` - modifyD, err := makeRoot(ctx, repo.ObjectRepo(), models.NewRootTreeEntry(commitB.Commit().TreeHash), testData) + modifyD, err := makeRoot(ctx, repo.FileTreeRepo(), models.NewRootTreeEntry(commitB.Commit().TreeHash), testData) require.NoError(t, err) mergeWipD, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeRef.ID, commitB.Commit().Hash, modifyD.Hash) require.NoError(t, err) @@ -199,7 +201,7 @@ func TestCommitOpMerge(t *testing.T) { testData = ` 2|a.txt |h4 ` - modifyE, err := makeRoot(ctx, repo.ObjectRepo(), models.NewRootTreeEntry(commitD.Commit().TreeHash), testData) + modifyE, err := makeRoot(ctx, repo.FileTreeRepo(), models.NewRootTreeEntry(commitD.Commit().TreeHash), testData) require.NoError(t, err) mergeWipE, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeRef.ID, commitD.Commit().Hash, modifyE.Hash) require.NoError(t, err) @@ -248,7 +250,7 @@ func TestCrissCrossMerge(t *testing.T) { 1|a.txt |h1 1|b.txt |h2 ` - oriRoot, err := makeRoot(ctx, repo.ObjectRepo(), EmptyDirEntry, testData) + oriRoot, err := makeRoot(ctx, repo.FileTreeRepo(), EmptyDirEntry, testData) require.NoError(t, err) //base branch baseRef, err := makeRef(ctx, repo.RefRepo(), "feat/base", project.ID, hash.Hash("a")) @@ -265,7 +267,7 @@ func TestCrissCrossMerge(t *testing.T) { 3|a.txt |h1 3|b.txt |h3 ` - baseModify, err := makeRoot(ctx, repo.ObjectRepo(), models.NewRootTreeEntry(oriRoot.Hash), testData) + baseModify, err := makeRoot(ctx, repo.FileTreeRepo(), models.NewRootTreeEntry(oriRoot.Hash), testData) require.NoError(t, err) baseWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseRef.ID, oriRoot.Hash, baseModify.Hash) @@ -284,7 +286,7 @@ func TestCrissCrossMerge(t *testing.T) { 3|a.txt |h4 3|b.txt |h2 ` - mergeModify, err := makeRoot(ctx, repo.ObjectRepo(), models.NewRootTreeEntry(oriRoot.Hash), testData) + mergeModify, err := makeRoot(ctx, repo.FileTreeRepo(), models.NewRootTreeEntry(oriRoot.Hash), testData) require.NoError(t, err) mergeWip, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeRef.ID, oriRoot.Hash, mergeModify.Hash) require.NoError(t, err) @@ -329,10 +331,9 @@ func makeRepository(ctx context.Context, repoRepo models.IRepositoryRepo, name s } // nolint -func makeCommit(ctx context.Context, commitRepo models.IObjectRepo, treeHash hash.Hash, msg string, parentsHash ...hash.Hash) (*models.Commit, error) { +func makeCommit(ctx context.Context, commitRepo models.ICommitRepo, treeHash hash.Hash, msg string, parentsHash ...hash.Hash) (*models.Commit, error) { commit := &models.Commit{ Hash: hash.Hash("mock"), - Type: models.CommitObject, Author: models.Signature{ Name: "admin", Email: "xxx@gg.com", @@ -347,11 +348,11 @@ func makeCommit(ctx context.Context, commitRepo models.IObjectRepo, treeHash has ParentHashes: parentsHash, Message: msg, } - obj, err := commitRepo.Insert(ctx, commit.Object()) + obj, err := commitRepo.Insert(ctx, commit) if err != nil { return nil, err } - return obj.Commit(), nil + return obj, nil } func makeRef(ctx context.Context, refRepo models.IRefRepo, name string, repoID uuid.UUID, commitHash hash.Hash) (*models.Ref, error) { @@ -380,7 +381,7 @@ func makeWip(ctx context.Context, wipRepo models.IWipRepo, repoID, refID uuid.UU return wipRepo.Insert(ctx, wip) } -func makeRoot(ctx context.Context, objRepo models.IObjectRepo, treeEntry models.TreeEntry, testData string) (*models.TreeNode, error) { +func makeRoot(ctx context.Context, objRepo models.IFileTreeRepo, treeEntry models.TreeEntry, testData string) (*models.TreeNode, error) { lines := strings.Split(testData, "\n") treeOp, err := NewWorkTree(ctx, objRepo, treeEntry) if err != nil { @@ -394,11 +395,12 @@ func makeRoot(ctx context.Context, objRepo models.IObjectRepo, treeEntry models. fullPath := strings.TrimSpace(commitData[1]) fileHash := strings.TrimSpace(commitData[2]) blob := &models.Blob{ - Hash: hash.Hash(fileHash), - Type: models.BlobObject, - Size: 10, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), + Hash: hash.Hash(fileHash), + Type: models.BlobObject, + Size: 10, + Properties: models.Property{Mode: filemode.Regular}, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), } if commitData[0] == "1" { diff --git a/versionmgr/merge_base_test.go b/versionmgr/merge_base_test.go index c8a3e66a..c329f083 100644 --- a/versionmgr/merge_base_test.go +++ b/versionmgr/merge_base_test.go @@ -19,7 +19,7 @@ func TestCommitNodeMergeBase(t *testing.T) { postgres, _, db := testhelper.SetupDatabase(ctx, t) defer postgres.Stop() //nolint - objRepo := models.NewObjectRepo(db) + commitRepo := models.NewCommitRepo(db) //mock data // | -> c ------- // | | @@ -37,7 +37,7 @@ f2|f1 f|f2 e|b ` - commitMap, err := loadCommitTestData(ctx, objRepo, testData) + commitMap, err := loadCommitTestData(ctx, commitRepo, testData) require.NoError(t, err) t.Run("simple", func(t *testing.T) { @@ -70,7 +70,7 @@ e|b }) } -func loadCommitTestData(ctx context.Context, objRepo models.IObjectRepo, testData string) (map[string]*CommitNode, error) { +func loadCommitTestData(ctx context.Context, commitRepo models.ICommitRepo, testData string) (map[string]*CommitNode, error) { lines := strings.Split(testData, "\n") commitMap := make(map[string]*CommitNode) for _, line := range lines { @@ -80,8 +80,8 @@ func loadCommitTestData(ctx context.Context, objRepo models.IObjectRepo, testDat commitData := strings.Split(strings.TrimSpace(line), "|") hashName := strings.TrimSpace(commitData[0]) commit := newCommit(hashName, strings.Split(commitData[1], ",")) - commitMap[hashName] = NewCommitNode(ctx, commit, objRepo) - _, err := objRepo.Insert(ctx, commit.Object()) + commitMap[hashName] = NewCommitNode(ctx, commit, commitRepo) + _, err := commitRepo.Insert(ctx, commit) if err != nil { return nil, err } @@ -100,14 +100,13 @@ func newCommit(hashStr string, parentHash []string) *models.Commit { } return &models.Commit{ Hash: hash.Hash(hashStr), - Type: models.CommitObject, Author: models.Signature{}, Committer: models.Signature{ When: time.Now(), }, MergeTag: "", Message: hashStr, - TreeHash: nil, + TreeHash: hash.Hash{}, ParentHashes: p, CreatedAt: time.Time{}, UpdatedAt: time.Time{}, diff --git a/versionmgr/merkletrie/README.md b/versionmgr/merkletrie/README.md new file mode 100644 index 00000000..155a1669 --- /dev/null +++ b/versionmgr/merkletrie/README.md @@ -0,0 +1 @@ +get this code from go-git, change litter to adjust property diff diff --git a/versionmgr/merkletrie/change.go b/versionmgr/merkletrie/change.go new file mode 100644 index 00000000..c1b352d5 --- /dev/null +++ b/versionmgr/merkletrie/change.go @@ -0,0 +1,149 @@ +package merkletrie + +import ( + "fmt" + "io" + + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" +) + +// Action values represent the kind of things a Change can represent: +// insertion, deletions or modifications of files. +type Action int + +// The set of possible actions in a change. +const ( + _ Action = iota + Insert + Delete + Modify +) + +// String returns the action as a human readable text. +func (a Action) String() string { + switch a { + case Insert: + return "Insert" + case Delete: + return "Delete" + case Modify: + return "Modify" + default: + panic(fmt.Sprintf("unsupported action: %d", a)) + } +} + +// A Change value represent how a noder has change between to merkletries. +type Change struct { + // The noder before the change or nil if it was inserted. + From noder.Path + // The noder after the change or nil if it was deleted. + To noder.Path +} + +// Action is convenience method that returns what Action c represents. +func (c *Change) Action() (Action, error) { + if c.From == nil && c.To == nil { + return Action(0), fmt.Errorf("malformed change: nil from and to") + } + if c.From == nil { + return Insert, nil + } + if c.To == nil { + return Delete, nil + } + + return Modify, nil +} + +// NewInsert returns a new Change representing the insertion of n. +func NewInsert(n noder.Path) Change { return Change{To: n} } + +// NewDelete returns a new Change representing the deletion of n. +func NewDelete(n noder.Path) Change { return Change{From: n} } + +// NewModify returns a new Change representing that a has been modified and +// it is now b. +func NewModify(a, b noder.Path) Change { + return Change{ + From: a, + To: b, + } +} + +// String returns a single change in human readable form, using the +// format: '<' + action + space + path + '>'. The contents of the file +// before or after the change are not included in this format. +// +// Example: inserting a file at the path a/b/c.txt will return "". +func (c Change) String() string { + action, err := c.Action() + if err != nil { + panic(err) + } + + var path string + if action == Delete { + path = c.From.String() + } else { + path = c.To.String() + } + + return fmt.Sprintf("<%s %s>", action, path) +} + +// Changes is a list of changes between to merkletries. +type Changes []Change + +// NewChanges returns an empty list of changes. +func NewChanges() Changes { + return Changes{} +} + +// Add adds the change c to the list of changes. +func (l *Changes) Add(c Change) { + *l = append(*l, c) +} + +// AddRecursiveInsert adds the required changes to insert all the +// file-like noders found in root, recursively. +func (l *Changes) AddRecursiveInsert(root noder.Path) error { + return l.addRecursive(root, NewInsert) +} + +// AddRecursiveDelete adds the required changes to delete all the +// file-like noders found in root, recursively. +func (l *Changes) AddRecursiveDelete(root noder.Path) error { + return l.addRecursive(root, NewDelete) +} + +type noderToChangeFn func(noder.Path) Change // NewInsert or NewDelete + +func (l *Changes) addRecursive(root noder.Path, ctor noderToChangeFn) error { + if !root.IsDir() { + l.Add(ctor(root)) + return nil + } + + i, err := NewIterFromPath(root) + if err != nil { + return err + } + + var current noder.Path + for { + if current, err = i.Step(); err != nil { + if err == io.EOF { + break + } + return err + } + if current.IsDir() { + continue + } + l.Add(ctor(current)) + } + + return nil +} diff --git a/versionmgr/merkletrie/change_test.go b/versionmgr/merkletrie/change_test.go new file mode 100644 index 00000000..349142c0 --- /dev/null +++ b/versionmgr/merkletrie/change_test.go @@ -0,0 +1,76 @@ +package merkletrie_test + +import ( + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/internal/fsnoder" + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" + + . "gopkg.in/check.v1" //nolint +) + +type ChangeSuite struct{} + +var _ = Suite(&ChangeSuite{}) + +func (s *ChangeSuite) TestActionString(c *C) { + action := merkletrie.Insert + c.Assert(action.String(), Equals, "Insert") + + action = merkletrie.Delete + c.Assert(action.String(), Equals, "Delete") + + action = merkletrie.Modify + c.Assert(action.String(), Equals, "Modify") +} + +func (s *ChangeSuite) TestUnsupportedAction(c *C) { + a := merkletrie.Action(42) + c.Assert(a.String, PanicMatches, "unsupported action.*") +} + +func (s ChangeSuite) TestNewInsert(c *C) { + tree, err := fsnoder.New("(a(b(z<>)))") + c.Assert(err, IsNil) + path := find(c, tree, "z") + change := merkletrie.NewInsert(path) + c.Assert(change.String(), Equals, "") + + shortPath := noder.Path([]noder.Noder{path.Last()}) + change = merkletrie.NewInsert(shortPath) + c.Assert(change.String(), Equals, "") +} + +func (s ChangeSuite) TestNewDelete(c *C) { + tree, err := fsnoder.New("(a(b(z<>)))") + c.Assert(err, IsNil) + path := find(c, tree, "z") + change := merkletrie.NewDelete(path) + c.Assert(change.String(), Equals, "") + + shortPath := noder.Path([]noder.Noder{path.Last()}) + change = merkletrie.NewDelete(shortPath) + c.Assert(change.String(), Equals, "") +} + +func (s ChangeSuite) TestNewModify(c *C) { + tree1, err := fsnoder.New("(a(b(z<>)))") + c.Assert(err, IsNil) + path1 := find(c, tree1, "z") + + tree2, err := fsnoder.New("(a(b(z<1>)))") + c.Assert(err, IsNil) + path2 := find(c, tree2, "z") + + change := merkletrie.NewModify(path1, path2) + c.Assert(change.String(), Equals, "") + + shortPath1 := noder.Path([]noder.Noder{path1.Last()}) + shortPath2 := noder.Path([]noder.Noder{path2.Last()}) + change = merkletrie.NewModify(shortPath1, shortPath2) + c.Assert(change.String(), Equals, "") +} + +func (s ChangeSuite) TestMalformedChange(c *C) { + change := merkletrie.Change{} + c.Assert(change.String, PanicMatches, "malformed change.*") +} diff --git a/versionmgr/merkletrie/difftree.go b/versionmgr/merkletrie/difftree.go new file mode 100644 index 00000000..062f0fb8 --- /dev/null +++ b/versionmgr/merkletrie/difftree.go @@ -0,0 +1,486 @@ +package merkletrie + +// The focus of this difftree implementation is to save time by +// skipping whole directories if their hash is the same in both +// trees. +// +// The diff algorithm implemented here is based on the doubleiter +// type defined in this same package; we will iterate over both +// trees at the same time, while comparing the current noders in +// each iterator. Depending on how they differ we will output the +// corresponding changes and move the iterators further over both +// trees. +// +// The table bellow show all the possible comparison results, along +// with what changes should we produce and how to advance the +// iterators. +// +// The table is implemented by the switches in this function, +// diffTwoNodes, diffTwoNodesSameName and diffTwoDirs. +// +// Many Bothans died to bring us this information, make sure you +// understand the table before modifying this code. + +// # Cases +// +// When comparing noders in both trees you will find yourself in +// one of 169 possible cases, but if we ignore moves, we can +// simplify a lot the search space into the following table: +// +// - "-": nothing, no file or directory +// - a<>: an empty file named "a". +// - a<1>: a file named "a", with "1" as its contents. +// - a<2>: a file named "a", with "2" as its contents. +// - a(): an empty dir named "a". +// - a(...): a dir named "a", with some files and/or dirs inside (possibly +// empty). +// - a(;;;): a dir named "a", with some other files and/or dirs inside +// (possibly empty), which different from the ones in "a(...)". +// +// \ to - a<> a<1> a<2> a() a(...) a(;;;) +// from \ +// - 00 01 02 03 04 05 06 +// a<> 10 11 12 13 14 15 16 +// a<1> 20 21 22 23 24 25 26 +// a<2> 30 31 32 33 34 35 36 +// a() 40 41 42 43 44 45 46 +// a(...) 50 51 52 53 54 55 56 +// a(;;;) 60 61 62 63 64 65 66 +// +// Every (from, to) combination in the table is a special case, but +// some of them can be merged into some more general cases, for +// instance 11 and 22 can be merged into the general case: both +// noders are equal. +// +// Here is a full list of all the cases that are similar and how to +// merge them together into more general cases. Each general case +// is labeled with an uppercase letter for further reference, and it +// is followed by the pseudocode of the checks you have to perform +// on both noders to see if you are in such a case, the actions to +// perform (i.e. what changes to output) and how to advance the +// iterators of each tree to continue the comparison process. +// +// ## A. Impossible: 00 +// +// ## B. Same thing on both sides: 11, 22, 33, 44, 55, 66 +// - check: `SameName() && SameHash()` +// - action: do nothing. +// - advance: `FromNext(); ToNext()` +// +// ### C. To was created: 01, 02, 03, 04, 05, 06 +// - check: `DifferentName() && ToBeforeFrom()` +// - action: insertRecursively(to) +// - advance: `ToNext()` +// +// ### D. From was deleted: 10, 20, 30, 40, 50, 60 +// - check: `DifferentName() && FromBeforeTo()` +// - action: `DeleteRecursively(from)` +// - advance: `FromNext()` +// +// ### E. Empty file to file with contents: 12, 13 +// - check: `SameName() && DifferentHash() && FromIsFile() && +// ToIsFile() && FromIsEmpty()` +// - action: `modifyFile(from, to)` +// - advance: `FromNext()` or `FromStep()` +// +// ### E'. file with contents to empty file: 21, 31 +// - check: `SameName() && DifferentHash() && FromIsFile() && +// ToIsFile() && ToIsEmpty()` +// - action: `modifyFile(from, to)` +// - advance: `FromNext()` or `FromStep()` +// +// ### F. empty file to empty dir with the same name: 14 +// - check: `SameName() && FromIsFile() && FromIsEmpty() && +// ToIsDir() && ToIsEmpty()` +// - action: `DeleteFile(from); InsertEmptyDir(to)` +// - advance: `FromNext(); ToNext()` +// +// ### F'. empty dir to empty file of the same name: 41 +// - check: `SameName() && FromIsDir() && FromIsEmpty && +// ToIsFile() && ToIsEmpty()` +// - action: `DeleteEmptyDir(from); InsertFile(to)` +// - advance: `FromNext(); ToNext()` or step for any of them. +// +// ### G. empty file to non-empty dir of the same name: 15, 16 +// - check: `SameName() && FromIsFile() && ToIsDir() && +// FromIsEmpty() && ToIsNotEmpty()` +// - action: `DeleteFile(from); InsertDirRecursively(to)` +// - advance: `FromNext(); ToNext()` +// +// ### G'. non-empty dir to empty file of the same name: 51, 61 +// - check: `SameName() && FromIsDir() && FromIsNotEmpty() && +// ToIsFile() && FromIsEmpty()` +// - action: `DeleteDirRecursively(from); InsertFile(to)` +// - advance: `FromNext(); ToNext()` +// +// ### H. modify file contents: 23, 32 +// - check: `SameName() && FromIsFile() && ToIsFile() && +// FromIsNotEmpty() && ToIsNotEmpty()` +// - action: `ModifyFile(from, to)` +// - advance: `FromNext(); ToNext()` +// +// ### I. file with contents to empty dir: 24, 34 +// - check: `SameName() && DifferentHash() && FromIsFile() && +// FromIsNotEmpty() && ToIsDir() && ToIsEmpty()` +// - action: `DeleteFile(from); InsertEmptyDir(to)` +// - advance: `FromNext(); ToNext()` +// +// ### I'. empty dir to file with contents: 42, 43 +// - check: `SameName() && DifferentHash() && FromIsDir() && +// FromIsEmpty() && ToIsFile() && ToIsEmpty()` +// - action: `DeleteDir(from); InsertFile(to)` +// - advance: `FromNext(); ToNext()` +// +// ### J. file with contents to dir with contents: 25, 26, 35, 36 +// - check: `SameName() && DifferentHash() && FromIsFile() && +// FromIsNotEmpty() && ToIsDir() && ToIsNotEmpty()` +// - action: `DeleteFile(from); InsertDirRecursively(to)` +// - advance: `FromNext(); ToNext()` +// +// ### J'. dir with contents to file with contents: 52, 62, 53, 63 +// - check: `SameName() && DifferentHash() && FromIsDir() && +// FromIsNotEmpty() && ToIsFile() && ToIsNotEmpty()` +// - action: `DeleteDirRecursively(from); InsertFile(to)` +// - advance: `FromNext(); ToNext()` +// +// ### K. empty dir to dir with contents: 45, 46 +// - check: `SameName() && DifferentHash() && FromIsDir() && +// FromIsEmpty() && ToIsDir() && ToIsNotEmpty()` +// - action: `InsertChildrenRecursively(to)` +// - advance: `FromNext(); ToNext()` +// +// ### K'. dir with contents to empty dir: 54, 64 +// - check: `SameName() && DifferentHash() && FromIsDir() && +// FromIsEmpty() && ToIsDir() && ToIsNotEmpty()` +// - action: `DeleteChildrenRecursively(from)` +// - advance: `FromNext(); ToNext()` +// +// ### L. dir with contents to dir with different contents: 56, 65 +// - check: `SameName() && DifferentHash() && FromIsDir() && +// FromIsNotEmpty() && ToIsDir() && ToIsNotEmpty()` +// - action: nothing +// - advance: `FromStep(); ToStep()` +// +// + +// All these cases can be further simplified by a truth table +// reduction process, in which we gather similar checks together to +// make the final code easier to read and understand. +// +// The first 6 columns are the outputs of the checks to perform on +// both noders. I have labeled them 1 to 6, this is what they mean: +// +// 1: SameName() +// 2: SameHash() +// 3: FromIsDir() +// 4: ToIsDir() +// 5: FromIsEmpty() +// 6: ToIsEmpty() +// +// The from and to columns are a fsnoder example of the elements +// that you will find on each tree under the specified comparison +// results (columns 1 to 6). +// +// The type column identifies the case we are into, from the list above. +// +// The type' column identifies the new set of reduced cases, using +// lowercase letters, and they are explained after the table. +// +// The last column is the set of actions and advances for each case. +// +// "---" means impossible except in case of hash collision. +// +// advance meaning: +// - NN: from.Next(); to.Next() +// - SS: from.Step(); to.Step() +// +// 1 2 3 4 5 6 | from | to |type|type'|action ; advance +// ------------+--------+--------+----+------------------------------------ +// 0 0 0 0 0 0 | | | | | if !SameName() { +// . | | | | | if FromBeforeTo() { +// . | | | D | d | delete(from); from.Next() +// . | | | | | } else { +// . | | | C | c | insert(to); to.Next() +// . | | | | | } +// 0 1 1 1 1 1 | | | | | } +// 1 0 0 0 0 0 | a<1> | a<2> | H | e | modify(from, to); NN +// 1 0 0 0 0 1 | a<1> | a<> | E' | e | modify(from, to); NN +// 1 0 0 0 1 0 | a<> | a<1> | E | e | modify(from, to); NN +// 1 0 0 0 1 1 | ---- | ---- | | e | +// 1 0 0 1 0 0 | a<1> | a(...) | J | f | delete(from); insert(to); NN +// 1 0 0 1 0 1 | a<1> | a() | I | f | delete(from); insert(to); NN +// 1 0 0 1 1 0 | a<> | a(...) | G | f | delete(from); insert(to); NN +// 1 0 0 1 1 1 | a<> | a() | F | f | delete(from); insert(to); NN +// 1 0 1 0 0 0 | a(...) | a<1> | J' | f | delete(from); insert(to); NN +// 1 0 1 0 0 1 | a(...) | a<> | G' | f | delete(from); insert(to); NN +// 1 0 1 0 1 0 | a() | a<1> | I' | f | delete(from); insert(to); NN +// 1 0 1 0 1 1 | a() | a<> | F' | f | delete(from); insert(to); NN +// 1 0 1 1 0 0 | a(...) | a(;;;) | L | g | nothing; SS +// 1 0 1 1 0 1 | a(...) | a() | K' | h | deleteChildren(from); NN +// 1 0 1 1 1 0 | a() | a(...) | K | i | insertChildren(to); NN +// 1 0 1 1 1 1 | ---- | ---- | | | +// 1 1 0 0 0 0 | a<1> | a<1> | B | b | nothing; NN +// 1 1 0 0 0 1 | ---- | ---- | | b | +// 1 1 0 0 1 0 | ---- | ---- | | b | +// 1 1 0 0 1 1 | a<> | a<> | B | b | nothing; NN +// 1 1 0 1 0 0 | ---- | ---- | | b | +// 1 1 0 1 0 1 | ---- | ---- | | b | +// 1 1 0 1 1 0 | ---- | ---- | | b | +// 1 1 0 1 1 1 | ---- | ---- | | b | +// 1 1 1 0 0 0 | ---- | ---- | | b | +// 1 1 1 0 0 1 | ---- | ---- | | b | +// 1 1 1 0 1 0 | ---- | ---- | | b | +// 1 1 1 0 1 1 | ---- | ---- | | b | +// 1 1 1 1 0 0 | a(...) | a(...) | B | b | nothing; NN +// 1 1 1 1 0 1 | ---- | ---- | | b | +// 1 1 1 1 1 0 | ---- | ---- | | b | +// 1 1 1 1 1 1 | a() | a() | B | b | nothing; NN +// +// c and d: +// if !SameName() +// d if FromBeforeTo() +// c else +// b: SameName) && sameHash() +// e: SameName() && !sameHash() && BothAreFiles() +// f: SameName() && !sameHash() && FileAndDir() +// g: SameName() && !sameHash() && BothAreDirs() && NoneIsEmpty +// i: SameName() && !sameHash() && BothAreDirs() && FromIsEmpty +// h: else of i + +import ( + "context" + "errors" + "fmt" + + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" +) + +var ( + // ErrCanceled is returned whenever the operation is canceled. + ErrCanceled = errors.New("operation canceled") +) + +// DiffTree calculates the list of changes between two merkletries. It +// uses the provided hashEqual callback to compare noders. +func DiffTree( + fromTree, + toTree noder.Noder, +) (Changes, error) { + return DiffTreeContext(context.Background(), fromTree, toTree) +} + +// DiffTreeContext calculates the list of changes between two merkletries. It +// uses the provided hashEqual callback to compare noders. +// Error will be returned if context expires +// Provided context must be non nil +func DiffTreeContext(ctx context.Context, fromTree, toTree noder.Noder) (Changes, error) { + ret := NewChanges() + + ii, err := newDoubleIter(fromTree, toTree) + if err != nil { + return nil, err + } + + for { + select { + case <-ctx.Done(): + return nil, ErrCanceled + default: + } + + from := ii.from.current + to := ii.to.current + + switch r := ii.remaining(); r { + case noMoreNoders: + return ret, nil + case onlyFromRemains: + if err = ret.AddRecursiveDelete(from); err != nil { + return nil, err + } + if err = ii.nextFrom(); err != nil { + return nil, err + } + case onlyToRemains: + if to.Skip() { + if err = ret.AddRecursiveDelete(to); err != nil { + return nil, err + } + } else { + if err = ret.AddRecursiveInsert(to); err != nil { + return nil, err + } + } + if err = ii.nextTo(); err != nil { + return nil, err + } + case bothHaveNodes: + if from.Skip() { + if err = ret.AddRecursiveDelete(from); err != nil { + return nil, err + } + if err := ii.nextBoth(); err != nil { + return nil, err + } + break + } + if to.Skip() { + if err = ret.AddRecursiveDelete(to); err != nil { + return nil, err + } + if err := ii.nextBoth(); err != nil { + return nil, err + } + break + } + + if err = diffNodes(&ret, ii); err != nil { + return nil, err + } + default: + panic(fmt.Sprintf("unknown remaining value: %d", r)) + } + } +} + +func diffNodes(changes *Changes, ii *doubleIter) error { + from := ii.from.current + to := ii.to.current + var err error + + // compare their full paths as strings + switch from.Compare(to) { + case -1: + if err = changes.AddRecursiveDelete(from); err != nil { + return err + } + if err = ii.nextFrom(); err != nil { + return err + } + case 1: + if err = changes.AddRecursiveInsert(to); err != nil { + return err + } + if err = ii.nextTo(); err != nil { + return err + } + default: + if err := diffNodesSameName(changes, ii); err != nil { + return err + } + } + + return nil +} + +func diffNodesSameName(changes *Changes, ii *doubleIter) error { + from := ii.from.current + to := ii.to.current + + status, err := ii.compare() + if err != nil { + return err + } + + switch { + case status.sameHash: + // do nothing + if err = ii.nextBoth(); err != nil { + return err + } + case status.bothAreFiles: + changes.Add(NewModify(from, to)) + if err = ii.nextBoth(); err != nil { + return err + } + case status.fileAndDir: + if err = changes.AddRecursiveDelete(from); err != nil { + return err + } + if err = changes.AddRecursiveInsert(to); err != nil { + return err + } + if err = ii.nextBoth(); err != nil { + return err + } + case status.bothAreDirs: + isDirChildrenSame, err := isDiffChildrenSame(from.Last(), to.Last()) + if err != nil { + return err + } + if isDirChildrenSame { + changes.Add(Change{ + From: from, + To: to, + }) + + return ii.nextBoth() + } + if err = diffDirs(changes, ii); err != nil { + return err + } + default: + return fmt.Errorf("bad status from double iterator") + } + + return nil +} + +func diffDirs(changes *Changes, ii *doubleIter) error { + from := ii.from.current + to := ii.to.current + + status, err := ii.compare() + if err != nil { + return err + } + + switch { + case status.fromIsEmptyDir: + if err = changes.AddRecursiveInsert(to); err != nil { + return err + } + if err = ii.nextBoth(); err != nil { + return err + } + case status.toIsEmptyDir: + if err = changes.AddRecursiveDelete(from); err != nil { + return err + } + if err = ii.nextBoth(); err != nil { + return err + } + case !status.fromIsEmptyDir && !status.toIsEmptyDir: + // do nothing + if err = ii.stepBoth(); err != nil { + return err + } + default: + return fmt.Errorf("both dirs are empty but has different hash") + } + + return nil +} + +func isDiffChildrenSame(from, to noder.Noder) (bool, error) { + fromChildren, err := from.Children() + if err != nil { + return false, err + } + toChildren, err := to.Children() + if err != nil { + return false, err + } + + if len(fromChildren) != len(toChildren) { + return false, nil + } + + for index, fromChild := range fromChildren { + toChild := toChildren[index] + if !fromChild.Equal(toChild) { + return false, nil + } + } + return true, nil +} diff --git a/versionmgr/merkletrie/difftree_test.go b/versionmgr/merkletrie/difftree_test.go new file mode 100644 index 00000000..87d07855 --- /dev/null +++ b/versionmgr/merkletrie/difftree_test.go @@ -0,0 +1,523 @@ +package merkletrie_test + +import ( + "bytes" + ctx "context" + "fmt" + "reflect" + "sort" + "strings" + "testing" + "unicode" + + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/internal/fsnoder" + + . "gopkg.in/check.v1" //nolint +) + +func Test(t *testing.T) { TestingT(t) } + +type DiffTreeSuite struct{} + +var _ = Suite(&DiffTreeSuite{}) + +type diffTreeTest struct { + from string + to string + expected string +} + +func (t diffTreeTest) innerRun(c *C, context string, reverse bool) { + comment := Commentf("\n%s", context) + if reverse { + comment = Commentf("%s [REVERSED]", comment.CheckCommentString()) + } + + a, err := fsnoder.New(t.from) + c.Assert(err, IsNil, comment) + comment = Commentf("%s\n\t from = %s", comment.CheckCommentString(), a) + + b, err := fsnoder.New(t.to) + c.Assert(err, IsNil, comment) + comment = Commentf("%s\n\t to = %s", comment.CheckCommentString(), b) + + expected, err := newChangesFromString(t.expected) + c.Assert(err, IsNil, comment) + + if reverse { + a, b = b, a + expected = expected.reverse() + } + comment = Commentf("%s\n\texpected = %s", comment.CheckCommentString(), expected) + + results, err := merkletrie.DiffTree(a, b) + c.Assert(err, IsNil, comment) + + obtained, err := newChanges(results) + c.Assert(err, IsNil, comment) + + comment = Commentf("%s\n\tobtained = %s", comment.CheckCommentString(), obtained) + + c.Assert(obtained, changesEquals, expected, comment) +} + +func (t diffTreeTest) innerRunCtx(c *C, context string, reverse bool) { + comment := Commentf("\n%s", context) + if reverse { + comment = Commentf("%s [REVERSED]", comment.CheckCommentString()) + } + + a, err := fsnoder.New(t.from) + c.Assert(err, IsNil, comment) + comment = Commentf("%s\n\t from = %s", comment.CheckCommentString(), a) + + b, err := fsnoder.New(t.to) + c.Assert(err, IsNil, comment) + comment = Commentf("%s\n\t to = %s", comment.CheckCommentString(), b) + + expected, err := newChangesFromString(t.expected) + c.Assert(err, IsNil, comment) + + if reverse { + a, b = b, a + expected = expected.reverse() + } + comment = Commentf("%s\n\texpected = %s", comment.CheckCommentString(), expected) + + results, err := merkletrie.DiffTreeContext(ctx.Background(), a, b) + c.Assert(err, IsNil, comment) + + obtained, err := newChanges(results) + c.Assert(err, IsNil, comment) + + comment = Commentf("%s\n\tobtained = %s", comment.CheckCommentString(), obtained) + + c.Assert(obtained, changesEquals, expected, comment) +} + +func (t diffTreeTest) run(c *C, context string) { + t.innerRun(c, context, false) + t.innerRun(c, context, true) + t.innerRunCtx(c, context, false) + t.innerRunCtx(c, context, true) +} + +type change struct { + merkletrie.Action + path string +} + +func (c change) String() string { + return fmt.Sprintf("<%s %s>", c.Action, c.path) +} + +func (c change) reverse() change { + ret := change{ + path: c.path, + } + + switch c.Action { + case merkletrie.Insert: + ret.Action = merkletrie.Delete + case merkletrie.Delete: + ret.Action = merkletrie.Insert + case merkletrie.Modify: + ret.Action = merkletrie.Modify + default: + panic(fmt.Sprintf("unknown action type: %d", c.Action)) + } + + return ret +} + +type changes []change + +func newChanges(original merkletrie.Changes) (changes, error) { + ret := make(changes, len(original)) + for i, c := range original { + action, err := c.Action() + if err != nil { + return nil, err + } + switch action { + case merkletrie.Insert: + ret[i] = change{ + Action: merkletrie.Insert, + path: c.To.String(), + } + case merkletrie.Delete: + ret[i] = change{ + Action: merkletrie.Delete, + path: c.From.String(), + } + case merkletrie.Modify: + ret[i] = change{ + Action: merkletrie.Modify, + path: c.From.String(), + } + default: + panic(fmt.Sprintf("unsupported action %d", action)) + } + } + + return ret, nil +} + +func newChangesFromString(s string) (changes, error) { + ret := make([]change, 0) + + s = strings.TrimSpace(s) + s = removeDuplicatedSpace(s) + s = turnSpaceIntoLiteralSpace(s) + + if s == "" { + return ret, nil + } + + for _, chunk := range strings.Split(s, " ") { + change := change{ + path: chunk[1:], + } + + switch chunk[0] { + case '+': + change.Action = merkletrie.Insert + case '-': + change.Action = merkletrie.Delete + case '*': + change.Action = merkletrie.Modify + default: + panic(fmt.Sprintf("unsupported action descriptor %q", chunk[0])) + } + + ret = append(ret, change) + } + + return ret, nil +} + +func removeDuplicatedSpace(s string) string { + var buf bytes.Buffer + + var lastWasSpace, currentIsSpace bool + for _, r := range s { + currentIsSpace = unicode.IsSpace(r) + + if lastWasSpace && currentIsSpace { + continue + } + lastWasSpace = currentIsSpace + + buf.WriteRune(r) + } + + return buf.String() +} + +func turnSpaceIntoLiteralSpace(s string) string { + return strings.Map( + func(r rune) rune { + if unicode.IsSpace(r) { + return ' ' + } + return r + }, s) +} + +func (cc changes) Len() int { return len(cc) } +func (cc changes) Swap(i, j int) { cc[i], cc[j] = cc[j], cc[i] } +func (cc changes) Less(i, j int) bool { return strings.Compare(cc[i].String(), cc[j].String()) < 0 } + +func (cc changes) equals(other changes) bool { + sort.Sort(cc) + sort.Sort(other) + return reflect.DeepEqual(cc, other) +} + +func (cc changes) String() string { + var buf bytes.Buffer + fmt.Fprintf(&buf, "len(%d) [", len(cc)) + sep := "" + for _, c := range cc { + fmt.Fprintf(&buf, "%s%s", sep, c) + sep = ", " + } + buf.WriteByte(']') + return buf.String() +} + +func (cc changes) reverse() changes { + ret := make(changes, len(cc)) + for i, c := range cc { + ret[i] = c.reverse() + } + + return ret +} + +type changesEqualsChecker struct { + *CheckerInfo +} + +var changesEquals Checker = &changesEqualsChecker{ + &CheckerInfo{Name: "changesEquals", Params: []string{"obtained", "expected"}}, +} + +func (checker *changesEqualsChecker) Check(params []interface{}, _ []string) (result bool, error string) { + a, ok := params[0].(changes) + if !ok { + return false, "first parameter must be a changes" + } + b, ok := params[1].(changes) + if !ok { + return false, "second parameter must be a changes" + } + + return a.equals(b), "" +} + +func do(c *C, list []diffTreeTest) { + for i, t := range list { + t.run(c, fmt.Sprintf("test #%d:", i)) + } +} + +func (s *DiffTreeSuite) TestEmptyVsEmpty(c *C) { + do(c, []diffTreeTest{ + {"()", "()", ""}, + {"A()", "A()", ""}, + {"A()", "()", ""}, + {"A()", "B()", ""}, + }) +} + +func (s *DiffTreeSuite) TestBasicCases(c *C) { + do(c, []diffTreeTest{ + {"()", "()", ""}, + {"()", "(a<>)", "+a"}, + {"()", "(a<1>)", "+a"}, + {"()", "(a())", ""}, + {"()", "(a(b()))", ""}, + {"()", "(a(b<>))", "+a/b"}, + {"()", "(a(b<1>))", "+a/b"}, + {"(a<>)", "(a<>)", ""}, + {"(a<>)", "(a<1>)", "*a"}, + {"(a<>)", "(a())", "-a"}, + {"(a<>)", "(a(b()))", "-a"}, + {"(a<>)", "(a(b<>))", "-a +a/b"}, + {"(a<>)", "(a(b<1>))", "-a +a/b"}, + {"(a<>)", "(c())", "-a"}, + {"(a<>)", "(c(b()))", "-a"}, + {"(a<>)", "(c(b<>))", "-a +c/b"}, + {"(a<>)", "(c(b<1>))", "-a +c/b"}, + {"(a<>)", "(c(a()))", "-a"}, + {"(a<>)", "(c(a<>))", "-a +c/a"}, + {"(a<>)", "(c(a<1>))", "-a +c/a"}, + {"(a<1>)", "(a<1>)", ""}, + {"(a<1>)", "(a<2>)", "*a"}, + {"(a<1>)", "(b<1>)", "-a +b"}, + {"(a<1>)", "(b<2>)", "-a +b"}, + {"(a<1>)", "(a())", "-a"}, + {"(a<1>)", "(a(b()))", "-a"}, + {"(a<1>)", "(a(b<>))", "-a +a/b"}, + {"(a<1>)", "(a(b<1>))", "-a +a/b"}, + {"(a<1>)", "(a(b<2>))", "-a +a/b"}, + {"(a<1>)", "(c())", "-a"}, + {"(a<1>)", "(c(b()))", "-a"}, + {"(a<1>)", "(c(b<>))", "-a +c/b"}, + {"(a<1>)", "(c(b<1>))", "-a +c/b"}, + {"(a<1>)", "(c(b<2>))", "-a +c/b"}, + {"(a<1>)", "(c())", "-a"}, + {"(a<1>)", "(c(a()))", "-a"}, + {"(a<1>)", "(c(a<>))", "-a +c/a"}, + {"(a<1>)", "(c(a<1>))", "-a +c/a"}, + {"(a<1>)", "(c(a<2>))", "-a +c/a"}, + {"(a())", "(a())", ""}, + {"(a())", "(b())", ""}, + {"(a())", "(a(b()))", ""}, + {"(a())", "(b(a()))", ""}, + {"(a())", "(a(b<>))", "+a/b"}, + {"(a())", "(a(b<1>))", "+a/b"}, + {"(a())", "(b(a<>))", "+b/a"}, + {"(a())", "(b(a<1>))", "+b/a"}, + }) +} + +func (s *DiffTreeSuite) TestHorizontals(c *C) { + do(c, []diffTreeTest{ + {"()", "(a<> b<>)", "+a +b"}, + {"()", "(a<> b<1>)", "+a +b"}, + {"()", "(a<> b())", "+a"}, + {"()", "(a() b<>)", "+b"}, + {"()", "(a<1> b<>)", "+a +b"}, + {"()", "(a<1> b<1>)", "+a +b"}, + {"()", "(a<1> b<2>)", "+a +b"}, + {"()", "(a<1> b())", "+a"}, + {"()", "(a() b<1>)", "+b"}, + {"()", "(a() b())", ""}, + {"()", "(a<> b<> c<> d<>)", "+a +b +c +d"}, + {"()", "(a<> b<1> c() d<> e<2> f())", "+a +b +d +e"}, + }) +} + +func (s *DiffTreeSuite) TestVerticals(c *C) { + do(c, []diffTreeTest{ + {"()", "(z<>)", "+z"}, + {"()", "(a(z<>))", "+a/z"}, + {"()", "(a(b(z<>)))", "+a/b/z"}, + {"()", "(a(b(c(z<>))))", "+a/b/c/z"}, + {"()", "(a(b(c(d(z<>)))))", "+a/b/c/d/z"}, + {"()", "(a(b(c(d(z<1>)))))", "+a/b/c/d/z"}, + }) +} + +func (s *DiffTreeSuite) TestSingleInserts(c *C) { + do(c, []diffTreeTest{ + {"()", "(z<>)", "+z"}, + {"(a())", "(a(z<>))", "+a/z"}, + {"(a())", "(a(b(z<>)))", "+a/b/z"}, + {"(a(b(c())))", "(a(b(c(z<>))))", "+a/b/c/z"}, + {"(a<> b<> c<>)", "(a<> b<> c<> z<>)", "+z"}, + {"(a(b<> c<> d<>))", "(a(b<> c<> d<> z<>))", "+a/z"}, + {"(a(b(c<> d<> e<>)))", "(a(b(c<> d<> e<> z<>)))", "+a/b/z"}, + {"(a(b<>) f<>)", "(a(b<>) f<> z<>)", "+z"}, + {"(a(b<>) f<>)", "(a(b<> z<>) f<>)", "+a/z"}, + }) +} + +func (s *DiffTreeSuite) TestDebug(c *C) { + do(c, []diffTreeTest{ + {"(a(b<>) f<>)", "(a(b<> z<>) f<>)", "+a/z"}, + }) +} + +// root +// / | \ +// / | ---- +// f d h -------- +// /\ / \ | +// +// e a j b/ g +// | / \ | +// l n k icm +// +// | +// o +// | +// p/ +func (s *DiffTreeSuite) TestCrazy(c *C) { + crazy := "(f(e(l<1>) a(n(o(p())) k<1>)) d<1> h(j(i<1> c<2> m<>) b() g<>))" + do(c, []diffTreeTest{ + { + crazy, + "()", + "-d -f/e/l -f/a/k -h/j/i -h/j/c -h/j/m -h/g", + }, { + crazy, + crazy, + "", + }, { + crazy, + "(d<1>)", + "-f/e/l -f/a/k -h/j/i -h/j/c -h/j/m -h/g", + }, { + crazy, + "(d<1> h(b() g<>))", + "-f/e/l -f/a/k -h/j/i -h/j/c -h/j/m", + }, { + crazy, + "(d<1> f(e(l()) a()) h(b() g<>))", + "-f/e/l -f/a/k -h/j/i -h/j/c -h/j/m", + }, { + crazy, + "(d<1> f(e(l<1>) a()) h(b() g<>))", + "-f/a/k -h/j/i -h/j/c -h/j/m", + }, { + crazy, + "(d<2> f(e(l<2>) a(s(t<1>))) h(b() g<> r<> j(i<> c<3> m<>)))", + "+f/a/s/t +h/r -f/a/k *d *f/e/l *h/j/c *h/j/i", + }, { + crazy, + "(f(e(l<2>) a(n(o(p<1>)) k<>)) h(j(i<1> c<2> m<>) b() g<>))", + "*f/e/l +f/a/n/o/p *f/a/k -d", + }, { + crazy, + "(f(e(l<1>) a(n(o(p(r<1>))) k<1>)) d<1> h(j(i<1> c<2> b() m<>) g<1>))", + "+f/a/n/o/p/r *h/g", + }, + }) +} + +func (s *DiffTreeSuite) TestSameNames(c *C) { + do(c, []diffTreeTest{ + { + "(a(a(a<>)))", + "(a(a(a<1>)))", + "*a/a/a", + }, { + "(a(b(a<>)))", + "(a(b(a<>)) b(a<>))", + "+b/a", + }, { + "(a(b(a<>)))", + "(a(b()) b(a<>))", + "-a/b/a +b/a", + }, + }) +} + +func (s *DiffTreeSuite) TestIssue275(c *C) { + do(c, []diffTreeTest{ + { + "(a(b(c.go<1>) b.go<2>))", + "(a(b(c.go<1> d.go<3>) b.go<2>))", + "+a/b/d.go", + }, + }) +} + +func (s *DiffTreeSuite) TestIssue1057(c *C) { + p1 := "TestAppWithUnicodéPath" + p2 := "TestAppWithUnicodéPath" + c.Assert(p1 == p2, Equals, false) + do(c, []diffTreeTest{ + { + fmt.Sprintf("(%s(x.go<1>))", p1), + fmt.Sprintf("(%s(x.go<1>) %s(x.go<1>))", p1, p2), + fmt.Sprintf("+%s/x.go", p2), + }, + }) + // swap p1 with p2 + do(c, []diffTreeTest{ + { + fmt.Sprintf("(%s(x.go<1>))", p2), + fmt.Sprintf("(%s(x.go<1>) %s(x.go<1>))", p1, p2), + fmt.Sprintf("+%s/x.go", p1), + }, + }) +} + +func (s *DiffTreeSuite) TestCancel(c *C) { + t := diffTreeTest{"()", "(a<> b<1> c() d<> e<2> f())", "+a +b +d +e"} + comment := Commentf("\n%s", "test cancel:") + + a, err := fsnoder.New(t.from) + c.Assert(err, IsNil, comment) + comment = Commentf("%s\n\t from = %s", comment.CheckCommentString(), a) + + b, err := fsnoder.New(t.to) + c.Assert(err, IsNil, comment) + comment = Commentf("%s\n\t to = %s", comment.CheckCommentString(), b) + + expected, err := newChangesFromString(t.expected) + c.Assert(err, IsNil, comment) + + comment = Commentf("%s\n\texpected = %s", comment.CheckCommentString(), expected) + context, cancel := ctx.WithCancel(ctx.Background()) + cancel() + results, err := merkletrie.DiffTreeContext(context, a, b) + c.Assert(results, IsNil, comment) + c.Assert(err, ErrorMatches, "operation canceled") + +} diff --git a/versionmgr/merkletrie/doc.go b/versionmgr/merkletrie/doc.go new file mode 100644 index 00000000..5204024a --- /dev/null +++ b/versionmgr/merkletrie/doc.go @@ -0,0 +1,34 @@ +/* +Package merkletrie provides support for n-ary trees that are at the same +time Merkle trees and Radix trees (tries). + +Git trees are Radix n-ary trees in virtue of the names of their +tree entries. At the same time, git trees are Merkle trees thanks to +their hashes. + +This package defines Merkle tries as nodes that should have: + +- a hash: the Merkle part of the Merkle trie + +- a key: the Radix part of the Merkle trie + +The Merkle hash condition is not enforced by this package though. This +means that the hash of a node doesn't have to take into account the hashes of +their children, which is good for testing purposes. + +Nodes in the Merkle trie are abstracted by the Noder interface. The +intended use is that git trees implements this interface, either +directly or using a simple wrapper. + +This package provides an iterator for merkletries that can skip whole +directory-like noders and an efficient merkletrie comparison algorithm. + +When comparing git trees, the simple approach of alphabetically sorting +their elements and comparing the resulting lists is too slow as it +depends linearly on the number of files in the trees: When a directory +has lots of files but none of them has been modified, this approach is +very expensive. We can do better by prunning whole directories that +have not change, just by looking at their hashes. This package provides +the tools to do exactly that. +*/ +package merkletrie diff --git a/versionmgr/merkletrie/doubleiter.go b/versionmgr/merkletrie/doubleiter.go new file mode 100644 index 00000000..a86b5915 --- /dev/null +++ b/versionmgr/merkletrie/doubleiter.go @@ -0,0 +1,184 @@ +package merkletrie + +import ( + "fmt" + "io" + + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" +) + +// A doubleIter is a convenience type to keep track of the current +// noders in two merkletries that are going to be iterated in parallel. +// It has methods for: +// +// - iterating over the merkletries, both at the same time or +// individually: nextFrom, nextTo, nextBoth, stepBoth +// +// - checking if there are noders left in one or both of them with the +// remaining method and its associated returned type. +// +// - comparing the current noders of both merkletries in several ways, +// with the compare method and its associated returned type. +type doubleIter struct { + from struct { + iter *Iter + current noder.Path // nil if no more nodes + } + to struct { + iter *Iter + current noder.Path // nil if no more nodes + } +} + +// NewdoubleIter returns a new doubleIter for the merkletries "from" and +// "to". The hashEqual callback function will be used by the doubleIter +// to compare the hash of the noders in the merkletries. The doubleIter +// will be initialized to the first elements in each merkletrie if any. +func newDoubleIter(from, to noder.Noder) ( + *doubleIter, error) { + var ii doubleIter + var err error + + if ii.from.iter, err = NewIter(from); err != nil { + return nil, fmt.Errorf("from: %s", err) + } + if ii.from.current, err = ii.from.iter.Next(); turnEOFIntoNil(err) != nil { + return nil, fmt.Errorf("from: %s", err) + } + + if ii.to.iter, err = NewIter(to); err != nil { + return nil, fmt.Errorf("to: %s", err) + } + if ii.to.current, err = ii.to.iter.Next(); turnEOFIntoNil(err) != nil { + return nil, fmt.Errorf("to: %s", err) + } + + return &ii, nil +} + +func turnEOFIntoNil(e error) error { + if e != nil && e != io.EOF { + return e + } + return nil +} + +// NextBoth makes d advance to the next noder in both merkletries. If +// any of them is a directory, it skips its contents. +func (d *doubleIter) nextBoth() error { + if err := d.nextFrom(); err != nil { + return err + } + if err := d.nextTo(); err != nil { + return err + } + + return nil +} + +// NextFrom makes d advance to the next noder in the "from" merkletrie, +// skipping its contents if it is a directory. +func (d *doubleIter) nextFrom() (err error) { + d.from.current, err = d.from.iter.Next() + return turnEOFIntoNil(err) +} + +// NextTo makes d advance to the next noder in the "to" merkletrie, +// skipping its contents if it is a directory. +func (d *doubleIter) nextTo() (err error) { + d.to.current, err = d.to.iter.Next() + return turnEOFIntoNil(err) +} + +// StepBoth makes d advance to the next noder in both merkletries, +// getting deeper into directories if that is the case. +func (d *doubleIter) stepBoth() (err error) { + if d.from.current, err = d.from.iter.Step(); turnEOFIntoNil(err) != nil { + return err + } + if d.to.current, err = d.to.iter.Step(); turnEOFIntoNil(err) != nil { + return err + } + return nil +} + +// Remaining returns if there are no more noders in the tree, if both +// have noders or if one of them doesn't. +func (d *doubleIter) remaining() remaining { + if d.from.current == nil && d.to.current == nil { + return noMoreNoders + } + + if d.from.current == nil && d.to.current != nil { + return onlyToRemains + } + + if d.from.current != nil && d.to.current == nil { + return onlyFromRemains + } + + return bothHaveNodes +} + +// Remaining values tells you whether both trees still have noders, or +// only one of them or none of them. +type remaining int + +const ( + noMoreNoders remaining = iota + onlyToRemains + onlyFromRemains + bothHaveNodes +) + +// Compare returns the comparison between the current elements in the +// merkletries. +func (d *doubleIter) compare() (s comparison, err error) { + s.sameHash = d.from.current.Last().Equal(d.to.current.Last()) + + fromIsDir := d.from.current.IsDir() + toIsDir := d.to.current.IsDir() + + s.bothAreDirs = fromIsDir && toIsDir + s.bothAreFiles = !fromIsDir && !toIsDir + s.fileAndDir = !s.bothAreDirs && !s.bothAreFiles + + fromNumChildren, err := d.from.current.NumChildren() + if err != nil { + return comparison{}, fmt.Errorf("from: %s", err) + } + + toNumChildren, err := d.to.current.NumChildren() + if err != nil { + return comparison{}, fmt.Errorf("to: %s", err) + } + + s.fromIsEmptyDir = fromIsDir && fromNumChildren == 0 + s.toIsEmptyDir = toIsDir && toNumChildren == 0 + + return +} + +// Answers to a lot of questions you can ask about how to noders are +// equal or different. +type comparison struct { + // the following are only valid if both nodes have the same name + // (i.e. nameComparison == 0) + + // Do both nodes have the same hash? + sameHash bool + // Are both nodes files? + bothAreFiles bool + + // the following are only valid if any of the noders are dirs, + // this is, if !bothAreFiles + + // Is one a file and the other a dir? + fileAndDir bool + // Are both nodes dirs? + bothAreDirs bool + // Is the from node an empty dir? + fromIsEmptyDir bool + // Is the to Node an empty dir? + toIsEmptyDir bool +} diff --git a/versionmgr/merkletrie/internal/frame/frame.go b/versionmgr/merkletrie/internal/frame/frame.go new file mode 100644 index 00000000..f6a47b54 --- /dev/null +++ b/versionmgr/merkletrie/internal/frame/frame.go @@ -0,0 +1,92 @@ +package frame + +import ( + "bytes" + "fmt" + "sort" + "strings" + + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" +) + +// A Frame is a collection of siblings in a trie, sorted alphabetically +// by name. +type Frame struct { + // siblings, sorted in reverse alphabetical order by name + stack []noder.Noder +} + +type byName []noder.Noder + +func (a byName) Len() int { return len(a) } +func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byName) Less(i, j int) bool { + return strings.Compare(a[i].Name(), a[j].Name()) < 0 +} + +// New returns a frame with the children of the provided node. +func New(n noder.Noder) (*Frame, error) { + children, err := n.Children() + if err != nil { + return nil, err + } + + sort.Sort(sort.Reverse(byName(children))) + return &Frame{ + stack: children, + }, nil +} + +// String returns the quoted names of the noders in the frame sorted in +// alphabetical order by name, surrounded by square brackets and +// separated by comas. +// +// Examples: +// +// [] +// ["a", "b"] +func (f *Frame) String() string { + var buf bytes.Buffer + _ = buf.WriteByte('[') + + sep := "" + for i := f.Len() - 1; i >= 0; i-- { + _, _ = buf.WriteString(sep) + sep = ", " + _, _ = buf.WriteString(fmt.Sprintf("%q", f.stack[i].Name())) + } + + _ = buf.WriteByte(']') + + return buf.String() +} + +// First returns, but dont extract, the noder with the alphabetically +// smaller name in the frame and true if the frame was not empty. +// Otherwise it returns nil and false. +func (f *Frame) First() (noder.Noder, bool) { + if f.Len() == 0 { + return nil, false + } + + top := f.Len() - 1 + + return f.stack[top], true +} + +// Drop extracts the noder with the alphabetically smaller name in the +// frame or does nothing if the frame was empty. +func (f *Frame) Drop() { + if f.Len() == 0 { + return + } + + top := f.Len() - 1 + f.stack[top] = nil + f.stack = f.stack[:top] +} + +// Len returns the number of noders in the frame. +func (f *Frame) Len() int { + return len(f.stack) +} diff --git a/versionmgr/merkletrie/internal/frame/frame_test.go b/versionmgr/merkletrie/internal/frame/frame_test.go new file mode 100644 index 00000000..257f4656 --- /dev/null +++ b/versionmgr/merkletrie/internal/frame/frame_test.go @@ -0,0 +1,101 @@ +package frame + +import ( + "fmt" + "testing" + + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/internal/fsnoder" + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" + + . "gopkg.in/check.v1" //nolint +) + +func Test(t *testing.T) { TestingT(t) } + +type FrameSuite struct{} + +var _ = Suite(&FrameSuite{}) + +func (s *FrameSuite) TestNewFrameFromEmptyDir(c *C) { + A, err := fsnoder.New("A()") + c.Assert(err, IsNil) + + frame, err := New(A) + c.Assert(err, IsNil) + + expectedString := `[]` + c.Assert(frame.String(), Equals, expectedString) + + first, ok := frame.First() + c.Assert(first, IsNil) + c.Assert(ok, Equals, false) + + first, ok = frame.First() + c.Assert(first, IsNil) + c.Assert(ok, Equals, false) + + l := frame.Len() + c.Assert(l, Equals, 0) +} + +func (s *FrameSuite) TestNewFrameFromNonEmpty(c *C) { + // _______A/________ + // | / \ | + // x y B/ C/ + // | + // z + root, err := fsnoder.New("A(x<> y<> B() C(z<>))") + c.Assert(err, IsNil) + frame, err := New(root) + c.Assert(err, IsNil) + + expectedString := `["B", "C", "x", "y"]` + c.Assert(frame.String(), Equals, expectedString) + + l := frame.Len() + c.Assert(l, Equals, 4) + + checkFirstAndDrop(c, frame, "B", true) + l = frame.Len() + c.Assert(l, Equals, 3) + + checkFirstAndDrop(c, frame, "C", true) + l = frame.Len() + c.Assert(l, Equals, 2) + + checkFirstAndDrop(c, frame, "x", true) + l = frame.Len() + c.Assert(l, Equals, 1) + + checkFirstAndDrop(c, frame, "y", true) + l = frame.Len() + c.Assert(l, Equals, 0) + + checkFirstAndDrop(c, frame, "", false) + l = frame.Len() + c.Assert(l, Equals, 0) + + checkFirstAndDrop(c, frame, "", false) +} + +func checkFirstAndDrop(c *C, f *Frame, expectedNodeName string, expectedOK bool) { + first, ok := f.First() + c.Assert(ok, Equals, expectedOK) + if expectedOK { + c.Assert(first.Name(), Equals, expectedNodeName) + } + + f.Drop() +} + +// a mock noder that returns error when Children() is called +type errorNoder struct{ noder.Noder } + +func (e *errorNoder) Children() ([]noder.Noder, error) { + return nil, fmt.Errorf("mock error") +} + +func (s *FrameSuite) TestNewFrameErrors(c *C) { + _, err := New(&errorNoder{}) + c.Assert(err, ErrorMatches, "mock error") +} diff --git a/versionmgr/merkletrie/internal/fsnoder/dir.go b/versionmgr/merkletrie/internal/fsnoder/dir.go new file mode 100644 index 00000000..771e97a6 --- /dev/null +++ b/versionmgr/merkletrie/internal/fsnoder/dir.go @@ -0,0 +1,148 @@ +package fsnoder + +import ( + "bytes" + "fmt" + "hash/fnv" + "sort" + "strings" + + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" +) + +// Dir values implement directory-like noders. +type dir struct { + name string // relative + children []noder.Noder // sorted by name + hash []byte // memoized +} + +type byName []noder.Noder + +func (a byName) Len() int { return len(a) } +func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byName) Less(i, j int) bool { + return strings.Compare(a[i].Name(), a[j].Name()) < 0 +} + +// copies the children slice, so nobody can modify the order of its +// elements from the outside. +func newDir(name string, children []noder.Noder) (*dir, error) { + cloned := make([]noder.Noder, len(children)) + _ = copy(cloned, children) + sort.Sort(byName(cloned)) + + if hasChildrenWithNoName(cloned) { + return nil, fmt.Errorf("non-root inner nodes cannot have empty names") + } + + if hasDuplicatedNames(cloned) { + return nil, fmt.Errorf("children cannot have duplicated names") + } + + return &dir{ + name: name, + children: cloned, + }, nil +} + +func hasChildrenWithNoName(children []noder.Noder) bool { + for _, c := range children { + if c.Name() == "" { + return true + } + } + + return false +} + +func hasDuplicatedNames(children []noder.Noder) bool { + if len(children) < 2 { + return false + } + + for i := 1; i < len(children); i++ { + if children[i].Name() == children[i-1].Name() { + return true + } + } + + return false +} + +func (d *dir) Hash() []byte { + if d.hash == nil { + d.calculateHash() + } + + return d.hash +} + +func (d *dir) Equal(node noder.Noder) bool { + return bytes.Equal(d.Hash(), node.Hash()) +} + +// hash is calculated as the hash of "dir " plus the concatenation, for +// each child, of its name, a space and its hash. Children are sorted +// alphabetically before calculating the hash, so the result is unique. +func (d *dir) calculateHash() { + h := fnv.New64a() + h.Write([]byte("dir ")) + for _, c := range d.children { + h.Write([]byte(c.Name())) + h.Write([]byte(" ")) + h.Write(c.Hash()) + } + d.hash = h.Sum([]byte{}) +} + +func (d *dir) Name() string { + return d.name +} + +func (d *dir) IsDir() bool { + return true +} + +// returns a copy so nobody can alter the order of its elements from the +// outside. +func (d *dir) Children() ([]noder.Noder, error) { + clon := make([]noder.Noder, len(d.children)) + _ = copy(clon, d.children) + return clon, nil +} + +func (d *dir) NumChildren() (int, error) { + return len(d.children), nil +} + +func (d *dir) Skip() bool { + return false +} + +const ( + dirStartMark = '(' + dirEndMark = ')' + dirElementSep = ' ' +) + +// The string generated by this method is unique for each tree, as the +// children of each node are sorted alphabetically by name when +// generating the string. +func (d *dir) String() string { + var buf bytes.Buffer + + buf.WriteString(d.name) + buf.WriteRune(dirStartMark) + + for i, c := range d.children { + if i != 0 { + buf.WriteRune(dirElementSep) + } + buf.WriteString(c.String()) + } + + buf.WriteRune(dirEndMark) + + return buf.String() +} diff --git a/versionmgr/merkletrie/internal/fsnoder/dir_test.go b/versionmgr/merkletrie/internal/fsnoder/dir_test.go new file mode 100644 index 00000000..a1518c23 --- /dev/null +++ b/versionmgr/merkletrie/internal/fsnoder/dir_test.go @@ -0,0 +1,364 @@ +package fsnoder + +import ( + "reflect" + "sort" + + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" + + . "gopkg.in/check.v1" //nolint +) + +type DirSuite struct{} + +var _ = Suite(&DirSuite{}) + +func (s *DirSuite) TestIsDir(c *C) { + noName, err := newDir("", nil) + c.Assert(err, IsNil) + c.Assert(noName.IsDir(), Equals, true) + + empty, err := newDir("empty", nil) + c.Assert(err, IsNil) + c.Assert(empty.IsDir(), Equals, true) + + root, err := newDir("foo", []noder.Noder{empty}) + c.Assert(err, IsNil) + c.Assert(root.IsDir(), Equals, true) +} + +func assertChildren(c *C, n noder.Noder, expected []noder.Noder) { + numChildren, err := n.NumChildren() + c.Assert(err, IsNil) + c.Assert(numChildren, Equals, len(expected)) + + children, err := n.Children() + c.Assert(err, IsNil) + c.Assert(children, sortedSliceEquals, expected) +} + +type sortedSliceEqualsChecker struct { + *CheckerInfo +} + +var sortedSliceEquals Checker = &sortedSliceEqualsChecker{ + &CheckerInfo{ + Name: "sortedSliceEquals", + Params: []string{"obtained", "expected"}, + }, +} + +func (checker *sortedSliceEqualsChecker) Check( + params []interface{}, _ []string) (result bool, error string) { + a, ok := params[0].([]noder.Noder) + if !ok { + return false, "first parameter must be a []noder.Noder" + } + b, ok := params[1].([]noder.Noder) + if !ok { + return false, "second parameter must be a []noder.Noder" + } + sort.Sort(byName(a)) + sort.Sort(byName(b)) + + return reflect.DeepEqual(a, b), "" +} + +func (s *DirSuite) TestNewDirectoryNoNameAndEmpty(c *C) { + root, err := newDir("", nil) + c.Assert(err, IsNil) + + c.Assert(root.Hash(), DeepEquals, + []byte{0xca, 0x40, 0xf8, 0x67, 0x57, 0x8c, 0x32, 0x1c}) + c.Assert(root.Name(), Equals, "") + assertChildren(c, root, noder.NoChildren) + c.Assert(root.String(), Equals, "()") +} + +func (s *DirSuite) TestNewDirectoryEmpty(c *C) { + root, err := newDir("root", nil) + c.Assert(err, IsNil) + + c.Assert(root.Hash(), DeepEquals, + []byte{0xca, 0x40, 0xf8, 0x67, 0x57, 0x8c, 0x32, 0x1c}) + c.Assert(root.Name(), Equals, "root") + assertChildren(c, root, noder.NoChildren) + c.Assert(root.String(), Equals, "root()") +} + +func (s *DirSuite) TestEmptyDirsHaveSameHash(c *C) { + d1, err := newDir("foo", nil) + c.Assert(err, IsNil) + + d2, err := newDir("bar", nil) + c.Assert(err, IsNil) + + c.Assert(d1.Hash(), DeepEquals, d2.Hash()) +} + +func (s *DirSuite) TestNewDirWithEmptyDir(c *C) { + empty, err := newDir("empty", nil) + c.Assert(err, IsNil) + + root, err := newDir("", []noder.Noder{empty}) + c.Assert(err, IsNil) + + c.Assert(root.Hash(), DeepEquals, + []byte{0x39, 0x25, 0xa8, 0x99, 0x16, 0x47, 0x6a, 0x75}) + c.Assert(root.Name(), Equals, "") + assertChildren(c, root, []noder.Noder{empty}) + c.Assert(root.String(), Equals, "(empty())") +} + +func (s *DirSuite) TestNewDirWithOneEmptyFile(c *C) { + empty, err := newFile("name", "") + c.Assert(err, IsNil) + + root, err := newDir("", []noder.Noder{empty}) + c.Assert(err, IsNil) + c.Assert(root.Hash(), DeepEquals, + []byte{0xd, 0x4e, 0x23, 0x1d, 0xf5, 0x2e, 0xfa, 0xc2}) + c.Assert(root.Name(), Equals, "") + assertChildren(c, root, []noder.Noder{empty}) + c.Assert(root.String(), Equals, "(name<>)") +} + +func (s *DirSuite) TestNewDirWithOneFile(c *C) { + a, err := newFile("a", "1") + c.Assert(err, IsNil) + + root, err := newDir("", []noder.Noder{a}) + c.Assert(err, IsNil) + c.Assert(root.Hash(), DeepEquals, + []byte{0x96, 0xab, 0x29, 0x54, 0x2, 0x9e, 0x89, 0x28}) + c.Assert(root.Name(), Equals, "") + assertChildren(c, root, []noder.Noder{a}) + c.Assert(root.String(), Equals, "(a<1>)") +} + +func (s *DirSuite) TestDirsWithSameFileHaveSameHash(c *C) { + f1, err := newFile("a", "1") + c.Assert(err, IsNil) + r1, err := newDir("", []noder.Noder{f1}) + c.Assert(err, IsNil) + + f2, err := newFile("a", "1") + c.Assert(err, IsNil) + r2, err := newDir("", []noder.Noder{f2}) + c.Assert(err, IsNil) + + c.Assert(r1.Hash(), DeepEquals, r2.Hash()) +} + +func (s *DirSuite) TestDirsWithDifferentFileContentHaveDifferentHash(c *C) { + f1, err := newFile("a", "1") + c.Assert(err, IsNil) + r1, err := newDir("", []noder.Noder{f1}) + c.Assert(err, IsNil) + + f2, err := newFile("a", "2") + c.Assert(err, IsNil) + r2, err := newDir("", []noder.Noder{f2}) + c.Assert(err, IsNil) + + c.Assert(r1.Hash(), Not(DeepEquals), r2.Hash()) +} + +func (s *DirSuite) TestDirsWithDifferentFileNameHaveDifferentHash(c *C) { + f1, err := newFile("a", "1") + c.Assert(err, IsNil) + r1, err := newDir("", []noder.Noder{f1}) + c.Assert(err, IsNil) + + f2, err := newFile("b", "1") + c.Assert(err, IsNil) + r2, err := newDir("", []noder.Noder{f2}) + c.Assert(err, IsNil) + + c.Assert(r1.Hash(), Not(DeepEquals), r2.Hash()) +} + +func (s *DirSuite) TestDirsWithDifferentFileHaveDifferentHash(c *C) { + f1, err := newFile("a", "1") + c.Assert(err, IsNil) + r1, err := newDir("", []noder.Noder{f1}) + c.Assert(err, IsNil) + + f2, err := newFile("b", "2") + c.Assert(err, IsNil) + r2, err := newDir("", []noder.Noder{f2}) + c.Assert(err, IsNil) + + c.Assert(r1.Hash(), Not(DeepEquals), r2.Hash()) +} + +func (s *DirSuite) TestDirWithEmptyDirHasDifferentHashThanEmptyDir(c *C) { + f, err := newFile("a", "") + c.Assert(err, IsNil) + r1, err := newDir("", []noder.Noder{f}) + c.Assert(err, IsNil) + + d, err := newDir("a", nil) + c.Assert(err, IsNil) + r2, err := newDir("", []noder.Noder{d}) + c.Assert(err, IsNil) + + c.Assert(r1.Hash(), Not(DeepEquals), r2.Hash()) +} + +func (s *DirSuite) TestNewDirWithTwoFilesSameContent(c *C) { + a1, err := newFile("a", "1") + c.Assert(err, IsNil) + b1, err := newFile("b", "1") + c.Assert(err, IsNil) + + root, err := newDir("", []noder.Noder{a1, b1}) + c.Assert(err, IsNil) + + c.Assert(root.Hash(), DeepEquals, + []byte{0xc7, 0xc4, 0xbf, 0x70, 0x33, 0xb9, 0x57, 0xdb}) + c.Assert(root.Name(), Equals, "") + assertChildren(c, root, []noder.Noder{b1, a1}) + c.Assert(root.String(), Equals, "(a<1> b<1>)") +} + +func (s *DirSuite) TestNewDirWithTwoFilesDifferentContent(c *C) { + a1, err := newFile("a", "1") + c.Assert(err, IsNil) + b2, err := newFile("b", "2") + c.Assert(err, IsNil) + + root, err := newDir("", []noder.Noder{a1, b2}) + c.Assert(err, IsNil) + + c.Assert(root.Hash(), DeepEquals, + []byte{0x94, 0x8a, 0x9d, 0x8f, 0x6d, 0x98, 0x34, 0x55}) + c.Assert(root.Name(), Equals, "") + assertChildren(c, root, []noder.Noder{b2, a1}) +} + +func (s *DirSuite) TestCrazy(c *C) { + // "" + // | + // ------------------------- + // | | | | | + // a1 B c1 d2 E + // | | + // ------------- E + // | | | | | + // A B X c1 E + // | | + // a1 e1 + e1, err := newFile("e", "1") + c.Assert(err, IsNil) + E, err := newDir("e", []noder.Noder{e1}) + c.Assert(err, IsNil) + E, err = newDir("e", []noder.Noder{E}) + c.Assert(err, IsNil) + E, err = newDir("e", []noder.Noder{E}) + c.Assert(err, IsNil) + + A, err := newDir("a", nil) + c.Assert(err, IsNil) + B, err := newDir("b", nil) + c.Assert(err, IsNil) + a1, err := newFile("a", "1") + c.Assert(err, IsNil) + X, err := newDir("x", []noder.Noder{a1}) + c.Assert(err, IsNil) + c1, err := newFile("c", "1") + c.Assert(err, IsNil) + B, err = newDir("b", []noder.Noder{c1, B, X, A}) + c.Assert(err, IsNil) + + a1, err = newFile("a", "1") + c.Assert(err, IsNil) + c1, err = newFile("c", "1") + c.Assert(err, IsNil) + d2, err := newFile("d", "2") + c.Assert(err, IsNil) + + root, err := newDir("", []noder.Noder{a1, d2, E, B, c1}) + c.Assert(err, IsNil) + + c.Assert(root.Hash(), DeepEquals, + []byte{0xc3, 0x72, 0x9d, 0xf1, 0xcc, 0xec, 0x6d, 0xbb}) + c.Assert(root.Name(), Equals, "") + assertChildren(c, root, []noder.Noder{E, c1, B, a1, d2}) + c.Assert(root.String(), Equals, + "(a<1> b(a() b() c<1> x(a<1>)) c<1> d<2> e(e(e(e<1>))))") +} + +func (s *DirSuite) TestDirCannotHaveDirWithNoName(c *C) { + noName, err := newDir("", nil) + c.Assert(err, IsNil) + + _, err = newDir("", []noder.Noder{noName}) + c.Assert(err, Not(IsNil)) +} + +func (s *DirSuite) TestDirCannotHaveDuplicatedFiles(c *C) { + f1, err := newFile("a", "1") + c.Assert(err, IsNil) + + f2, err := newFile("a", "1") + c.Assert(err, IsNil) + + _, err = newDir("", []noder.Noder{f1, f2}) + c.Assert(err, Not(IsNil)) +} + +func (s *DirSuite) TestDirCannotHaveDuplicatedFileNames(c *C) { + a1, err := newFile("a", "1") + c.Assert(err, IsNil) + + a2, err := newFile("a", "2") + c.Assert(err, IsNil) + + _, err = newDir("", []noder.Noder{a1, a2}) + c.Assert(err, Not(IsNil)) +} + +func (s *DirSuite) TestDirCannotHaveDuplicatedDirNames(c *C) { + d1, err := newDir("a", nil) + c.Assert(err, IsNil) + + d2, err := newDir("a", nil) + c.Assert(err, IsNil) + + _, err = newDir("", []noder.Noder{d1, d2}) + c.Assert(err, Not(IsNil)) +} + +func (s *DirSuite) TestDirCannotHaveDirAndFileWithSameName(c *C) { + f, err := newFile("a", "") + c.Assert(err, IsNil) + + d, err := newDir("a", nil) + c.Assert(err, IsNil) + + _, err = newDir("", []noder.Noder{f, d}) + c.Assert(err, Not(IsNil)) +} + +func (s *DirSuite) TestUnsortedString(c *C) { + b, err := newDir("b", nil) + c.Assert(err, IsNil) + + z, err := newDir("z", nil) + c.Assert(err, IsNil) + + a1, err := newFile("a", "1") + c.Assert(err, IsNil) + + c2, err := newFile("c", "2") + c.Assert(err, IsNil) + + d3, err := newFile("d", "3") + c.Assert(err, IsNil) + + d, err := newDir("d", []noder.Noder{c2, z, d3, a1, b}) + c.Assert(err, IsNil) + + c.Assert(d.String(), Equals, "d(a<1> b() c<2> d<3> z())") +} diff --git a/versionmgr/merkletrie/internal/fsnoder/doc.go b/versionmgr/merkletrie/internal/fsnoder/doc.go new file mode 100644 index 00000000..fe3aab7e --- /dev/null +++ b/versionmgr/merkletrie/internal/fsnoder/doc.go @@ -0,0 +1,52 @@ +/* +Package fsnoder allows to create merkletrie noders that resemble file +systems, from human readable string descriptions. Its intended use is +generating noders in tests in a readable way. + +For example: + + root, _ = New("(a<1> b<2>, B(c<3> d()))") + +will create a noder as follows: + + root - "root" is an unnamed dir containing "a", "b" and "B". + / | \ - "a" is a file containing the string "1". + / | \ - "b" is a file containing the string "2". + a b B - "B" is a directory containing "c" and "d". + / \ - "c" is a file containing the string "3". + c d - "D" is an empty directory. + +Files are expressed as: + +- one or more letters and dots for the name of the file + +- a single number, between angle brackets, for the contents of the file. + +- examples: a<1>, foo.go<2>. + +Directories are expressed as: + +- one or more letters for the name of the directory. + +- its elements between parents, separated with spaces, in any order. + +- (optionally) the root directory can be unnamed, by skipping its name. + +Examples: + +- D(a<1> b<2>) : two files, "a" and "b", having "1" and "2" as their +respective contents, inside a directory called "D". + +- A() : An empty directory called "A". + +- A(b<>) : An directory called "A", with an empty file inside called "b": + +- (b(c<1> d(e<2>)) f<>) : an unamed directory containing: + + ├── b --> directory + │   ├── c --> file containing "1" + │   └── d --> directory + │   └── e --> file containing "2" + └── f --> empty file +*/ +package fsnoder diff --git a/versionmgr/merkletrie/internal/fsnoder/file.go b/versionmgr/merkletrie/internal/fsnoder/file.go new file mode 100644 index 00000000..10bdae55 --- /dev/null +++ b/versionmgr/merkletrie/internal/fsnoder/file.go @@ -0,0 +1,80 @@ +package fsnoder + +import ( + "bytes" + "fmt" + "hash/fnv" + + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" +) + +// file values represent file-like noders in a merkle trie. +type file struct { + name string // relative + contents string + hash []byte // memoized +} + +// newFile returns a noder representing a file with the given contents. +func newFile(name, contents string) (*file, error) { + if name == "" { + return nil, fmt.Errorf("files cannot have empty names") + } + + return &file{ + name: name, + contents: contents, + }, nil +} + +// The hash of a file is just its contents. +// Empty files will have the fnv64 basis offset as its hash. +func (f *file) Hash() []byte { + if f.hash == nil { + h := fnv.New64a() + h.Write([]byte(f.contents)) // it never returns an error. + f.hash = h.Sum(nil) + } + + return f.hash +} + +func (f *file) Equal(other noder.Noder) bool { + return bytes.Equal(f.Hash(), other.Hash()) +} + +func (f *file) Name() string { + return f.name +} + +func (f *file) IsDir() bool { + return false +} + +func (f *file) Children() ([]noder.Noder, error) { + return noder.NoChildren, nil +} + +func (f *file) NumChildren() (int, error) { + return 0, nil +} + +func (f *file) Skip() bool { + return false +} + +const ( + fileStartMark = '<' + fileEndMark = '>' +) + +// String returns a string formatted as: name. +func (f *file) String() string { + var buf bytes.Buffer + buf.WriteString(f.name) + buf.WriteRune(fileStartMark) + buf.WriteString(f.contents) + buf.WriteRune(fileEndMark) + + return buf.String() +} diff --git a/versionmgr/merkletrie/internal/fsnoder/file_test.go b/versionmgr/merkletrie/internal/fsnoder/file_test.go new file mode 100644 index 00000000..96fab96c --- /dev/null +++ b/versionmgr/merkletrie/internal/fsnoder/file_test.go @@ -0,0 +1,67 @@ +package fsnoder + +import ( + "testing" + + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" + + . "gopkg.in/check.v1" //nolint +) + +func Test(t *testing.T) { TestingT(t) } + +type FileSuite struct{} + +var _ = Suite(&FileSuite{}) + +var ( + HashOfEmptyFile = []byte{0xcb, 0xf2, 0x9c, 0xe4, 0x84, 0x22, 0x23, 0x25} // fnv64 basis offset + HashOfContents = []byte{0xee, 0x7e, 0xf3, 0xd0, 0xc2, 0xb5, 0xef, 0x83} // hash of "contents" +) + +func (s *FileSuite) TestNewFileEmpty(c *C) { + f, err := newFile("name", "") + c.Assert(err, IsNil) + + c.Assert(f.Hash(), DeepEquals, HashOfEmptyFile) + c.Assert(f.Name(), Equals, "name") + c.Assert(f.IsDir(), Equals, false) + assertChildren(c, f, noder.NoChildren) + c.Assert(f.String(), Equals, "name<>") +} + +func (s *FileSuite) TestNewFileWithContents(c *C) { + f, err := newFile("name", "contents") + c.Assert(err, IsNil) + + c.Assert(f.Hash(), DeepEquals, HashOfContents) + c.Assert(f.Name(), Equals, "name") + c.Assert(f.IsDir(), Equals, false) + assertChildren(c, f, noder.NoChildren) + c.Assert(f.String(), Equals, "name") +} + +func (s *FileSuite) TestNewfileErrorEmptyName(c *C) { + _, err := newFile("", "contents") + c.Assert(err, Not(IsNil)) +} + +func (s *FileSuite) TestDifferentContentsHaveDifferentHash(c *C) { + f1, err := newFile("name", "contents") + c.Assert(err, IsNil) + + f2, err := newFile("name", "foo") + c.Assert(err, IsNil) + + c.Assert(f1.Hash(), Not(DeepEquals), f2.Hash()) +} + +func (s *FileSuite) TestSameContentsHaveSameHash(c *C) { + f1, err := newFile("name1", "contents") + c.Assert(err, IsNil) + + f2, err := newFile("name2", "contents") + c.Assert(err, IsNil) + + c.Assert(f1.Hash(), DeepEquals, f2.Hash()) +} diff --git a/versionmgr/merkletrie/internal/fsnoder/new.go b/versionmgr/merkletrie/internal/fsnoder/new.go new file mode 100644 index 00000000..188086ac --- /dev/null +++ b/versionmgr/merkletrie/internal/fsnoder/new.go @@ -0,0 +1,194 @@ +package fsnoder + +import ( + "bytes" + "fmt" + "io" + + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" +) + +// New function creates a full merkle trie from the string description of +// a filesystem tree. See examples of the string format in the package +// description. +func New(s string) (noder.Noder, error) { + return decodeDir([]byte(s), root) +} + +const ( + root = true + nonRoot = false +) + +// Expected data: a fsnoder description, for example: A(foo bar qux ...). +// When isRoot is true, unnamed dirs are supported, for example: (foo +// bar qux ...) +func decodeDir(data []byte, isRoot bool) (*dir, error) { + data = bytes.TrimSpace(data) + if len(data) == 0 { + return nil, io.EOF + } + + // get the name of the dir and remove it from the data. In case the + // there is no name and isRoot is true, just use "" as the name. + var name string + switch end := bytes.IndexRune(data, dirStartMark); end { + case -1: + return nil, fmt.Errorf("%c not found", dirStartMark) + case 0: + if isRoot { + name = "" + } else { + return nil, fmt.Errorf("inner unnamed dirs not allowed: %s", data) + } + default: + name = string(data[0:end]) + data = data[end:] + } + + // check data ends with the dirEndMark + if data[len(data)-1] != dirEndMark { + return nil, fmt.Errorf("malformed data: last %q not found", + dirEndMark) + } + data = data[1 : len(data)-1] // remove initial '(' and last ')' + + children, err := decodeChildren(data) + if err != nil { + return nil, err + } + + return newDir(name, children) +} + +func isNumber(b rune) bool { + return '0' <= b && b <= '9' +} + +func isLetter(b rune) bool { + return ('a' <= b && b <= 'z') || ('A' <= b && b <= 'Z') +} + +func decodeChildren(data []byte) ([]noder.Noder, error) { + data = bytes.TrimSpace(data) + if len(data) == 0 { + return nil, nil + } + + chunks := split(data) + ret := make([]noder.Noder, len(chunks)) + var err error + for i, c := range chunks { + ret[i], err = decodeChild(c) + if err != nil { + return nil, fmt.Errorf("malformed element %d (%s): %s", i, c, err) + } + } + + return ret, nil +} + +// returns the description of the elements of a dir. It is just looking +// for spaces if they are not part of inner dirs. +func split(data []byte) [][]byte { + chunks := [][]byte{} + + start := 0 + dirDepth := 0 + for i, b := range data { + switch b { + case dirStartMark: + dirDepth++ + case dirEndMark: + dirDepth-- + case dirElementSep: + if dirDepth == 0 { + chunks = append(chunks, data[start:i+1]) + start = i + 1 + } + } + } + chunks = append(chunks, data[start:]) + + return chunks +} + +// A child can be a file or a dir. +func decodeChild(data []byte) (noder.Noder, error) { + clean := bytes.TrimSpace(data) + if len(data) < 3 { + return nil, fmt.Errorf("element too short: %s", clean) + } + + fileNameEnd := bytes.IndexRune(data, fileStartMark) + dirNameEnd := bytes.IndexRune(data, dirStartMark) + switch { + case fileNameEnd == -1 && dirNameEnd == -1: + return nil, fmt.Errorf( + "malformed child, no file or dir start mark found") + case fileNameEnd == -1: + return decodeDir(clean, nonRoot) + case dirNameEnd == -1: + return decodeFile(clean) + case dirNameEnd < fileNameEnd: + return decodeDir(clean, nonRoot) + case dirNameEnd > fileNameEnd: + return decodeFile(clean) + } + + return nil, fmt.Errorf("unreachable") +} + +func decodeFile(data []byte) (noder.Noder, error) { + nameEnd := bytes.IndexRune(data, fileStartMark) + if nameEnd == -1 { + return nil, fmt.Errorf("malformed file, no %c found", fileStartMark) + } + contentStart := nameEnd + 1 + contentEnd := bytes.IndexRune(data, fileEndMark) + if contentEnd == -1 { + return nil, fmt.Errorf("malformed file, no %c found", fileEndMark) + } + + switch { + case nameEnd > contentEnd: + return nil, fmt.Errorf("malformed file, found %c before %c", + fileEndMark, fileStartMark) + case contentStart == contentEnd: + name := string(data[:nameEnd]) + if !validFileName(name) { + return nil, fmt.Errorf("invalid file name") + } + return newFile(name, "") + default: + name := string(data[:nameEnd]) + if !validFileName(name) { + return nil, fmt.Errorf("invalid file name") + } + contents := string(data[contentStart:contentEnd]) + if !validFileContents(contents) { + return nil, fmt.Errorf("invalid file contents") + } + return newFile(name, contents) + } +} + +func validFileName(s string) bool { + for _, c := range s { + if !isLetter(c) && c != '.' { + return false + } + } + + return true +} + +func validFileContents(s string) bool { + for _, c := range s { + if !isNumber(c) { + return false + } + } + + return true +} diff --git a/versionmgr/merkletrie/internal/fsnoder/new_test.go b/versionmgr/merkletrie/internal/fsnoder/new_test.go new file mode 100644 index 00000000..3e03e576 --- /dev/null +++ b/versionmgr/merkletrie/internal/fsnoder/new_test.go @@ -0,0 +1,332 @@ +package fsnoder + +import ( + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" + + . "gopkg.in/check.v1" //nolint +) + +type FSNoderSuite struct{} + +var _ = Suite(&FSNoderSuite{}) + +func check(c *C, input string, expected *dir) { + obtained, err := New(input) + c.Assert(err, IsNil, Commentf("input = %s", input)) + + comment := Commentf("\n input = %s\n"+ + "expected = %s\nobtained = %s", + input, expected, obtained) + c.Assert(obtained.Hash(), DeepEquals, expected.Hash(), comment) +} + +func (s *FSNoderSuite) TestNoDataFails(c *C) { + _, err := New("") + c.Assert(err, Not(IsNil)) + + _, err = New(" ") // SPC + TAB + c.Assert(err, Not(IsNil)) +} + +func (s *FSNoderSuite) TestUnnamedRootFailsIfNotRoot(c *C) { + _, err := decodeDir([]byte("()"), false) + c.Assert(err, Not(IsNil)) +} + +func (s *FSNoderSuite) TestUnnamedInnerFails(c *C) { + _, err := New("(())") + c.Assert(err, Not(IsNil)) + _, err = New("((a<>))") + c.Assert(err, Not(IsNil)) +} + +func (s *FSNoderSuite) TestMalformedFile(c *C) { + _, err := New("(4<>)") + c.Assert(err, Not(IsNil)) + _, err = New("(4<1>)") + c.Assert(err, Not(IsNil)) + _, err = New("(4?1>)") + c.Assert(err, Not(IsNil)) + _, err = New("(4)") + c.Assert(err, Not(IsNil)) + _, err = New("(4")) + c.Assert(err, Not(IsNil)) + _, err = decodeFile([]byte("a")) + c.Assert(err, Not(IsNil)) + _, err = decodeFile([]byte("a<1?")) + c.Assert(err, Not(IsNil)) + + _, err = decodeFile([]byte("a?>")) + c.Assert(err, Not(IsNil)) + _, err = decodeFile([]byte("1<>")) + c.Assert(err, Not(IsNil)) + _, err = decodeFile([]byte("a") + c.Assert(err, Not(IsNil)) + _, err = New("a<>") + c.Assert(err, Not(IsNil)) +} + +func (s *FSNoderSuite) TestUnnamedEmptyRoot(c *C) { + input := "()" + + expected, err := newDir("", nil) + c.Assert(err, IsNil) + + check(c, input, expected) +} + +func (s *FSNoderSuite) TestNamedEmptyRoot(c *C) { + input := "a()" + + expected, err := newDir("a", nil) + c.Assert(err, IsNil) + + check(c, input, expected) +} + +func (s *FSNoderSuite) TestEmptyFile(c *C) { + input := "(a<>)" + + a1, err := newFile("a", "") + c.Assert(err, IsNil) + expected, err := newDir("", []noder.Noder{a1}) + c.Assert(err, IsNil) + + check(c, input, expected) +} + +func (s *FSNoderSuite) TestNonEmptyFile(c *C) { + input := "(a<1>)" + + a1, err := newFile("a", "1") + c.Assert(err, IsNil) + expected, err := newDir("", []noder.Noder{a1}) + c.Assert(err, IsNil) + + check(c, input, expected) +} + +func (s *FSNoderSuite) TestTwoFilesSameContents(c *C) { + input := "(b<1> a<1>)" + + a1, err := newFile("a", "1") + c.Assert(err, IsNil) + b1, err := newFile("b", "1") + c.Assert(err, IsNil) + expected, err := newDir("", []noder.Noder{a1, b1}) + c.Assert(err, IsNil) + + check(c, input, expected) +} + +func (s *FSNoderSuite) TestTwoFilesDifferentContents(c *C) { + input := "(b<2> a<1>)" + + a1, err := newFile("a", "1") + c.Assert(err, IsNil) + b2, err := newFile("b", "2") + c.Assert(err, IsNil) + expected, err := newDir("", []noder.Noder{a1, b2}) + c.Assert(err, IsNil) + + check(c, input, expected) +} + +func (s *FSNoderSuite) TestManyFiles(c *C) { + input := "(e<1> b<2> a<1> c<1> d<3> f<4>)" + + a1, err := newFile("a", "1") + c.Assert(err, IsNil) + b2, err := newFile("b", "2") + c.Assert(err, IsNil) + c1, err := newFile("c", "1") + c.Assert(err, IsNil) + d3, err := newFile("d", "3") + c.Assert(err, IsNil) + e1, err := newFile("e", "1") + c.Assert(err, IsNil) + f4, err := newFile("f", "4") + c.Assert(err, IsNil) + expected, err := newDir("", []noder.Noder{e1, b2, a1, c1, d3, f4}) + c.Assert(err, IsNil) + + check(c, input, expected) +} + +func (s *FSNoderSuite) TestEmptyDir(c *C) { + input := "(A())" + + A, err := newDir("A", nil) + c.Assert(err, IsNil) + expected, err := newDir("", []noder.Noder{A}) + c.Assert(err, IsNil) + + check(c, input, expected) +} + +func (s *FSNoderSuite) TestDirWithEmptyFile(c *C) { + input := "(A(a<>))" + + a, err := newFile("a", "") + c.Assert(err, IsNil) + A, err := newDir("A", []noder.Noder{a}) + c.Assert(err, IsNil) + expected, err := newDir("", []noder.Noder{A}) + c.Assert(err, IsNil) + + check(c, input, expected) +} + +func (s *FSNoderSuite) TestDirWithEmptyFileSameName(c *C) { + input := "(A(A<>))" + + f, err := newFile("A", "") + c.Assert(err, IsNil) + A, err := newDir("A", []noder.Noder{f}) + c.Assert(err, IsNil) + expected, err := newDir("", []noder.Noder{A}) + c.Assert(err, IsNil) + + check(c, input, expected) +} + +func (s *FSNoderSuite) TestDirWithFileLongContents(c *C) { + input := "(A(a<12>))" + + a1, err := newFile("a", "12") + c.Assert(err, IsNil) + A, err := newDir("A", []noder.Noder{a1}) + c.Assert(err, IsNil) + expected, err := newDir("", []noder.Noder{A}) + c.Assert(err, IsNil) + + check(c, input, expected) +} + +func (s *FSNoderSuite) TestDirWithFileLongName(c *C) { + input := "(A(abc<12>))" + + a1, err := newFile("abc", "12") + c.Assert(err, IsNil) + A, err := newDir("A", []noder.Noder{a1}) + c.Assert(err, IsNil) + expected, err := newDir("", []noder.Noder{A}) + c.Assert(err, IsNil) + + check(c, input, expected) +} + +func (s *FSNoderSuite) TestDirWithFile(c *C) { + input := "(A(a<1>))" + + a1, err := newFile("a", "1") + c.Assert(err, IsNil) + A, err := newDir("A", []noder.Noder{a1}) + c.Assert(err, IsNil) + expected, err := newDir("", []noder.Noder{A}) + c.Assert(err, IsNil) + + check(c, input, expected) +} + +func (s *FSNoderSuite) TestDirWithEmptyDirSameName(c *C) { + input := "(A(A()))" + + A2, err := newDir("A", nil) + c.Assert(err, IsNil) + A1, err := newDir("A", []noder.Noder{A2}) + c.Assert(err, IsNil) + expected, err := newDir("", []noder.Noder{A1}) + c.Assert(err, IsNil) + + check(c, input, expected) +} + +func (s *FSNoderSuite) TestDirWithEmptyDir(c *C) { + input := "(A(B()))" + + B, err := newDir("B", nil) + c.Assert(err, IsNil) + A, err := newDir("A", []noder.Noder{B}) + c.Assert(err, IsNil) + expected, err := newDir("", []noder.Noder{A}) + c.Assert(err, IsNil) + + check(c, input, expected) +} + +func (s *FSNoderSuite) TestDirWithTwoFiles(c *C) { + input := "(A(a<1> b<2>))" + + a1, err := newFile("a", "1") + c.Assert(err, IsNil) + b2, err := newFile("b", "2") + c.Assert(err, IsNil) + A, err := newDir("A", []noder.Noder{b2, a1}) + c.Assert(err, IsNil) + expected, err := newDir("", []noder.Noder{A}) + c.Assert(err, IsNil) + + check(c, input, expected) +} + +func (s *FSNoderSuite) TestCrazy(c *C) { + // "" + // | + // ------------------------- + // | | | | | + // a1 B c1 d2 E + // | | + // ------------- E + // | | | | | + // A B X c1 E + // | | + // a1 e1 + input := "(d<2> b(c<1> b() a() x(a<1>)) a<1> c<1> e(e(e(e<1>))))" + + e1, err := newFile("e", "1") + c.Assert(err, IsNil) + E, err := newDir("e", []noder.Noder{e1}) + c.Assert(err, IsNil) + E, err = newDir("e", []noder.Noder{E}) + c.Assert(err, IsNil) + E, err = newDir("e", []noder.Noder{E}) + c.Assert(err, IsNil) + + A, err := newDir("a", nil) + c.Assert(err, IsNil) + B, err := newDir("b", nil) + c.Assert(err, IsNil) + a1, err := newFile("a", "1") + c.Assert(err, IsNil) + X, err := newDir("x", []noder.Noder{a1}) + c.Assert(err, IsNil) + c1, err := newFile("c", "1") + c.Assert(err, IsNil) + B, err = newDir("b", []noder.Noder{c1, B, X, A}) + c.Assert(err, IsNil) + + a1, err = newFile("a", "1") + c.Assert(err, IsNil) + c1, err = newFile("c", "1") + c.Assert(err, IsNil) + d2, err := newFile("d", "2") + c.Assert(err, IsNil) + + expected, err := newDir("", []noder.Noder{a1, d2, E, B, c1}) + c.Assert(err, IsNil) + + check(c, input, expected) +} diff --git a/versionmgr/merkletrie/iter.go b/versionmgr/merkletrie/iter.go new file mode 100644 index 00000000..7636de14 --- /dev/null +++ b/versionmgr/merkletrie/iter.go @@ -0,0 +1,215 @@ +package merkletrie + +import ( + "fmt" + "io" + + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/internal/frame" + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" +) + +// Iter is an iterator for merkletries (only the trie part of the +// merkletrie is relevant here, it does not use the Hasher interface). +// +// The iteration is performed in depth-first pre-order. Entries at each +// depth are traversed in (case-sensitive) alphabetical order. +// +// This is the kind of traversal you will expect when listing ordinary +// files and directories recursively, for example: +// +// Trie Traversal order +// ---- --------------- +// . +// / | \ c +// / | \ d/ +// d c z ===> d/a +// / \ d/b +// b a z +// +// This iterator is somewhat especial as you can chose to skip whole +// "directories" when iterating: +// +// - The Step method will iterate normally. +// +// - the Next method will not descend deeper into the tree. +// +// For example, if the iterator is at `d/`, the Step method will return +// `d/a` while the Next would have returned `z` instead (skipping `d/` +// and its descendants). The name of the these two methods are based on +// the well known "next" and "step" operations, quite common in +// debuggers, like gdb. +// +// The paths returned by the iterator will be relative, if the iterator +// was created from a single node, or absolute, if the iterator was +// created from the path to the node (the path will be prefixed to all +// returned paths). +type Iter struct { + // Tells if the iteration has started. + hasStarted bool + // The top of this stack has the current node and its siblings. The + // rest of the stack keeps the ancestors of the current node and + // their corresponding siblings. The current element is always the + // top element of the top frame. + // + // When "step"ping into a node, its children are pushed as a new + // frame. + // + // When "next"ing pass a node, the current element is dropped by + // popping the top frame. + frameStack []*frame.Frame + // The base path used to turn the relative paths used internally by + // the iterator into absolute paths used by external applications. + // For relative iterator this will be nil. + base noder.Path +} + +// NewIter returns a new relative iterator using the provider noder as +// its unnamed root. When iterating, all returned paths will be +// relative to node. +func NewIter(n noder.Noder) (*Iter, error) { + return newIter(n, nil) +} + +// NewIterFromPath returns a new absolute iterator from the noder at the +// end of the path p. When iterating, all returned paths will be +// absolute, using the root of the path p as their root. +func NewIterFromPath(p noder.Path) (*Iter, error) { + return newIter(p.Last(), p) // Path implements Noder +} + +func newIter(root noder.Noder, base noder.Path) (*Iter, error) { + ret := &Iter{ + base: base, + } + + if root == nil { + return ret, nil + } + + frame, err := frame.New(root) + if err != nil { + return nil, err + } + ret.push(frame) + + return ret, nil +} + +func (iter *Iter) top() (*frame.Frame, bool) { + if len(iter.frameStack) == 0 { + return nil, false + } + top := len(iter.frameStack) - 1 + + return iter.frameStack[top], true +} + +func (iter *Iter) push(f *frame.Frame) { + iter.frameStack = append(iter.frameStack, f) +} + +const ( + doDescend = true + dontDescend = false +) + +// Next returns the path of the next node without descending deeper into +// the trie and nil. If there are no more entries in the trie it +// returns nil and io.EOF. In case of error, it will return nil and the +// error. +func (iter *Iter) Next() (noder.Path, error) { + return iter.advance(dontDescend) +} + +// Step returns the path to the next node in the trie, descending deeper +// into it if needed, and nil. If there are no more nodes in the trie, +// it returns nil and io.EOF. In case of error, it will return nil and +// the error. +func (iter *Iter) Step() (noder.Path, error) { + return iter.advance(doDescend) +} + +// Advances the iterator in the desired direction: descend or +// dontDescend. +// +// Returns the new current element and a nil error on success. If there +// are no more elements in the trie below the base, it returns nil, and +// io.EOF. Returns nil and an error in case of errors. +func (iter *Iter) advance(wantDescend bool) (noder.Path, error) { + current, err := iter.current() + if err != nil { + return nil, err + } + + // The first time we just return the current node. + if !iter.hasStarted { + iter.hasStarted = true + return current, nil + } + + // Advances means getting a next current node, either its first child or + // its next sibling, depending if we must descend or not. + numChildren, err := current.NumChildren() + if err != nil { + return nil, err + } + + mustDescend := numChildren != 0 && wantDescend + if mustDescend { + // descend: add a new frame with the current's children. + frame, err := frame.New(current.Last()) + if err != nil { + return nil, err + } + iter.push(frame) + } else { + // don't descend: just drop the current node + iter.drop() + } + + return iter.current() +} + +// Returns the path to the current node, adding the base if there was +// one, and a nil error. If there were no noders left, it returns nil +// and io.EOF. If an error occurred, it returns nil and the error. +func (iter *Iter) current() (noder.Path, error) { + if topFrame, ok := iter.top(); !ok { + return nil, io.EOF + } else if _, ok := topFrame.First(); !ok { + return nil, io.EOF + } + + ret := make(noder.Path, 0, len(iter.base)+len(iter.frameStack)) + + // concat the base... + ret = append(ret, iter.base...) + // ... and the current node and all its ancestors + for i, f := range iter.frameStack { + t, ok := f.First() + if !ok { + panic(fmt.Sprintf("frame %d is empty", i)) + } + ret = append(ret, t) + } + + return ret, nil +} + +// removes the current node if any, and all the frames that become empty as a +// consequence of this action. +func (iter *Iter) drop() { + frame, ok := iter.top() + if !ok { + return + } + + frame.Drop() + // if the frame is empty, remove it and its parent, recursively + if frame.Len() == 0 { + top := len(iter.frameStack) - 1 + iter.frameStack[top] = nil + iter.frameStack = iter.frameStack[:top] + iter.drop() + } +} diff --git a/versionmgr/merkletrie/iter_test.go b/versionmgr/merkletrie/iter_test.go new file mode 100644 index 00000000..6058c0a0 --- /dev/null +++ b/versionmgr/merkletrie/iter_test.go @@ -0,0 +1,474 @@ +package merkletrie_test + +import ( + "fmt" + "io" + "strings" + + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/internal/fsnoder" + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" + + . "gopkg.in/check.v1" //nolint +) + +type IterSuite struct{} + +var _ = Suite(&IterSuite{}) + +// A test is a list of operations we want to perform on an iterator and +// their expected results. +// +// The operations are expressed as a sequence of `n` and `s`, +// representing the amount of next and step operations we want to call +// on the iterator and their order. For example, an operations value of +// "nns" means: call a `n`ext, then another `n`ext and finish with a +// `s`tep. +// +// The expected is the full path of the noders returned by the +// operations, separated by spaces. +// +// For instance: +// +// t := test{ +// operations: "ns", +// expected: "a a/b" +// } +// +// means: +// +// - the first iterator operation has to be Next, and it must return a +// node called "a" with no ancestors. +// +// - the second operation has to be Step, and it must return a node +// called "b" with a single ancestor called "a". +type test struct { + operations string + expected string +} + +// Runs a test on the provided iterator, checking that the names of the +// returned values are correct. If not, the treeDescription value is +// printed along with information about mismatch. +func (t test) run(c *C, iter *merkletrie.Iter, + treeDescription string, testNumber int) { + + expectedChunks := strings.Split(t.expected, " ") + if t.expected == "" { + expectedChunks = []string{} + } + + if len(t.operations) < len(expectedChunks) { + c.Fatalf("malformed test %d: not enough operations", testNumber) + return + } + + var obtained noder.Path + var err error + for i, b := range t.operations { + comment := Commentf("\ntree: %q\ntest #%d (%q)\noperation #%d (%q)", + treeDescription, testNumber, t.operations, i, t.operations[i]) + + switch t.operations[i] { + case 'n': + obtained, err = iter.Next() + if err != io.EOF { + c.Assert(err, IsNil) + } + case 's': + obtained, err = iter.Step() + if err != io.EOF { + c.Assert(err, IsNil) + } + default: + c.Fatalf("unknown operation at test %d, operation %d (%c)\n", + testNumber, i, b) + } + if i >= len(expectedChunks) { + c.Assert(err, Equals, io.EOF, comment) + continue + } + + c.Assert(err, IsNil, comment) + c.Assert(obtained.String(), Equals, expectedChunks[i], comment) + } +} + +// A testsCollection value represents a tree and a collection of tests +// we want to perform on iterators of that tree. +// +// Example: +// +// . +// | +// --------- +// | | | +// a b c +// | +// z +// +// var foo testCollection = { +// tree: "(a<> b(z<>) c<>)" +// tests: []test{ +// {operations: "nns", expected: "a b b/z"}, +// {operations: "nnn", expected: "a b c"}, +// }, +// } +// +// A new iterator will be build for each test. +type testsCollection struct { + tree string // a fsnoder description of a tree. + tests []test // the collection of tests we want to run +} + +// Executes all the tests in a testsCollection. +func (tc testsCollection) run(c *C) { + root, err := fsnoder.New(tc.tree) + c.Assert(err, IsNil) + + for i, t := range tc.tests { + iter, err := merkletrie.NewIter(root) + c.Assert(err, IsNil) + t.run(c, iter, root.String(), i) + } +} + +func (s *IterSuite) TestEmptyNamedDir(c *C) { + tc := testsCollection{ + tree: "A()", + tests: []test{ + {operations: "n", expected: ""}, + {operations: "nn", expected: ""}, + {operations: "nnn", expected: ""}, + {operations: "nnns", expected: ""}, + {operations: "nnnssnsnns", expected: ""}, + {operations: "s", expected: ""}, + {operations: "ss", expected: ""}, + {operations: "sss", expected: ""}, + {operations: "sssn", expected: ""}, + {operations: "sssnnsnssn", expected: ""}, + }, + } + tc.run(c) +} + +func (s *IterSuite) TestEmptyUnnamedDir(c *C) { + tc := testsCollection{ + tree: "()", + tests: []test{ + {operations: "n", expected: ""}, + {operations: "nn", expected: ""}, + {operations: "nnn", expected: ""}, + {operations: "nnns", expected: ""}, + {operations: "nnnssnsnns", expected: ""}, + {operations: "s", expected: ""}, + {operations: "ss", expected: ""}, + {operations: "sss", expected: ""}, + {operations: "sssn", expected: ""}, + {operations: "sssnnsnssn", expected: ""}, + }, + } + tc.run(c) +} + +func (s *IterSuite) TestOneFile(c *C) { + tc := testsCollection{ + tree: "(a<>)", + tests: []test{ + {operations: "n", expected: "a"}, + {operations: "nn", expected: "a"}, + {operations: "nnn", expected: "a"}, + {operations: "nnns", expected: "a"}, + {operations: "nnnssnsnns", expected: "a"}, + {operations: "s", expected: "a"}, + {operations: "ss", expected: "a"}, + {operations: "sss", expected: "a"}, + {operations: "sssn", expected: "a"}, + {operations: "sssnnsnssn", expected: "a"}, + }, + } + tc.run(c) +} + +// root +// +// / \ +// +// a b +func (s *IterSuite) TestTwoFiles(c *C) { + tc := testsCollection{ + tree: "(a<> b<>)", + tests: []test{ + {operations: "nnn", expected: "a b"}, + {operations: "nns", expected: "a b"}, + {operations: "nsn", expected: "a b"}, + {operations: "nss", expected: "a b"}, + {operations: "snn", expected: "a b"}, + {operations: "sns", expected: "a b"}, + {operations: "ssn", expected: "a b"}, + {operations: "sss", expected: "a b"}, + }, + } + tc.run(c) +} + +// root +// +// | +// a +// | +// b +func (s *IterSuite) TestDirWithFile(c *C) { + tc := testsCollection{ + tree: "(a(b<>))", + tests: []test{ + {operations: "nnn", expected: "a"}, + {operations: "nns", expected: "a"}, + {operations: "nsn", expected: "a a/b"}, + {operations: "nss", expected: "a a/b"}, + {operations: "snn", expected: "a"}, + {operations: "sns", expected: "a"}, + {operations: "ssn", expected: "a a/b"}, + {operations: "sss", expected: "a a/b"}, + }, + } + tc.run(c) +} + +// root +// +// /|\ +// +// c a b +func (s *IterSuite) TestThreeSiblings(c *C) { + tc := testsCollection{ + tree: "(c<> a<> b<>)", + tests: []test{ + {operations: "nnnn", expected: "a b c"}, + {operations: "nnns", expected: "a b c"}, + {operations: "nnsn", expected: "a b c"}, + {operations: "nnss", expected: "a b c"}, + {operations: "nsnn", expected: "a b c"}, + {operations: "nsns", expected: "a b c"}, + {operations: "nssn", expected: "a b c"}, + {operations: "nsss", expected: "a b c"}, + {operations: "snnn", expected: "a b c"}, + {operations: "snns", expected: "a b c"}, + {operations: "snsn", expected: "a b c"}, + {operations: "snss", expected: "a b c"}, + {operations: "ssnn", expected: "a b c"}, + {operations: "ssns", expected: "a b c"}, + {operations: "sssn", expected: "a b c"}, + {operations: "ssss", expected: "a b c"}, + }, + } + tc.run(c) +} + +// root +// +// | +// b +// | +// c +// | +// a +func (s *IterSuite) TestThreeVertical(c *C) { + tc := testsCollection{ + tree: "(b(c(a())))", + tests: []test{ + {operations: "nnnn", expected: "b"}, + {operations: "nnns", expected: "b"}, + {operations: "nnsn", expected: "b"}, + {operations: "nnss", expected: "b"}, + {operations: "nsnn", expected: "b b/c"}, + {operations: "nsns", expected: "b b/c"}, + {operations: "nssn", expected: "b b/c b/c/a"}, + {operations: "nsss", expected: "b b/c b/c/a"}, + {operations: "snnn", expected: "b"}, + {operations: "snns", expected: "b"}, + {operations: "snsn", expected: "b"}, + {operations: "snss", expected: "b"}, + {operations: "ssnn", expected: "b b/c"}, + {operations: "ssns", expected: "b b/c"}, + {operations: "sssn", expected: "b b/c b/c/a"}, + {operations: "ssss", expected: "b b/c b/c/a"}, + }, + } + tc.run(c) +} + +// root +// +// / \ +// +// c a +// | +// b +func (s *IterSuite) TestThreeMix1(c *C) { + tc := testsCollection{ + tree: "(c(b<>) a<>)", + tests: []test{ + {operations: "nnnn", expected: "a c"}, + {operations: "nnns", expected: "a c"}, + {operations: "nnsn", expected: "a c c/b"}, + {operations: "nnss", expected: "a c c/b"}, + {operations: "nsnn", expected: "a c"}, + {operations: "nsns", expected: "a c"}, + {operations: "nssn", expected: "a c c/b"}, + {operations: "nsss", expected: "a c c/b"}, + {operations: "snnn", expected: "a c"}, + {operations: "snns", expected: "a c"}, + {operations: "snsn", expected: "a c c/b"}, + {operations: "snss", expected: "a c c/b"}, + {operations: "ssnn", expected: "a c"}, + {operations: "ssns", expected: "a c"}, + {operations: "sssn", expected: "a c c/b"}, + {operations: "ssss", expected: "a c c/b"}, + }, + } + tc.run(c) +} + +// root +// +// / \ +// +// b a +// +// | +// c +func (s *IterSuite) TestThreeMix2(c *C) { + tc := testsCollection{ + tree: "(b() a(c<>))", + tests: []test{ + {operations: "nnnn", expected: "a b"}, + {operations: "nnns", expected: "a b"}, + {operations: "nnsn", expected: "a b"}, + {operations: "nnss", expected: "a b"}, + {operations: "nsnn", expected: "a a/c b"}, + {operations: "nsns", expected: "a a/c b"}, + {operations: "nssn", expected: "a a/c b"}, + {operations: "nsss", expected: "a a/c b"}, + {operations: "snnn", expected: "a b"}, + {operations: "snns", expected: "a b"}, + {operations: "snsn", expected: "a b"}, + {operations: "snss", expected: "a b"}, + {operations: "ssnn", expected: "a a/c b"}, + {operations: "ssns", expected: "a a/c b"}, + {operations: "sssn", expected: "a a/c b"}, + {operations: "ssss", expected: "a a/c b"}, + }, + } + tc.run(c) +} + +// root +// / | \ +// / | ---- +// f d h -------- +// /\ / \ | +// +// e a j b/ g +// | / \ | +// l n k icm +// +// | +// o +// | +// p/ +func (s *IterSuite) TestCrazy(c *C) { + tc := testsCollection{ + tree: "(f(e(l<>) a(n(o(p())) k<>)) d<> h(j(i<> c<> m<>) b() g<>))", + tests: []test{ + {operations: "nnnnn", expected: "d f h"}, + {operations: "nnnns", expected: "d f h"}, + {operations: "nnnsn", expected: "d f h h/b h/g"}, + {operations: "nnnss", expected: "d f h h/b h/g"}, + {operations: "nnsnn", expected: "d f f/a f/e h"}, + {operations: "nnsns", expected: "d f f/a f/e f/e/l"}, + {operations: "nnssn", expected: "d f f/a f/a/k f/a/n"}, + {operations: "nnsss", expected: "d f f/a f/a/k f/a/n"}, + {operations: "nsnnn", expected: "d f h"}, + {operations: "nsnns", expected: "d f h"}, + {operations: "nsnsn", expected: "d f h h/b h/g"}, + {operations: "nsnss", expected: "d f h h/b h/g"}, + {operations: "nssnn", expected: "d f f/a f/e h"}, + }, + } + tc.run(c) +} + +// . +// | +// a +// | +// b +// / \ +// z h +// / \ +// +// d e +// +// | +// f +func (s *IterSuite) TestNewIterFromPath(c *C) { + tree, err := fsnoder.New("(a(b(z(d<> e(f<>)) h<>)))") + c.Assert(err, IsNil) + + z := find(c, tree, "z") + + iter, err := merkletrie.NewIterFromPath(z) + c.Assert(err, IsNil) + + n, err := iter.Next() + c.Assert(err, IsNil) + c.Assert(n.String(), Equals, "a/b/z/d") + + n, err = iter.Next() + c.Assert(err, IsNil) + c.Assert(n.String(), Equals, "a/b/z/e") + + n, err = iter.Step() + c.Assert(err, IsNil) + c.Assert(n.String(), Equals, "a/b/z/e/f") + + _, err = iter.Step() + c.Assert(err, Equals, io.EOF) +} + +func find(c *C, tree noder.Noder, name string) noder.Path { + iter, err := merkletrie.NewIter(tree) + c.Assert(err, IsNil) + + for { + current, err := iter.Step() + if err != io.EOF { + c.Assert(err, IsNil) + } else { + c.Fatalf("node %s not found in tree %s", name, tree) + } + + if current.Name() == name { + return current + } + } +} + +type errorNoder struct{ noder.Noder } + +func (e *errorNoder) Children() ([]noder.Noder, error) { + return nil, fmt.Errorf("mock error") +} + +func (s *IterSuite) TestNewIterNil(c *C) { + i, err := merkletrie.NewIter(nil) + c.Assert(err, IsNil) + _, err = i.Next() + c.Assert(err, Equals, io.EOF) +} + +func (s *IterSuite) TestNewIterFailsOnChildrenErrors(c *C) { + _, err := merkletrie.NewIter(&errorNoder{}) + c.Assert(err, ErrorMatches, "mock error") +} diff --git a/versionmgr/merkletrie/noder/noder.go b/versionmgr/merkletrie/noder/noder.go new file mode 100644 index 00000000..34ff82de --- /dev/null +++ b/versionmgr/merkletrie/noder/noder.go @@ -0,0 +1,55 @@ +// Package noder provide an interface for defining nodes in a +// merkletrie, their hashes and their paths (a noders and its +// ancestors). +// +// The hasher interface is easy to implement naively by elements that +// already have a hash, like git blobs and trees. More sophisticated +// implementations can implement the Equal function in exotic ways +// though: for instance, comparing the modification time of directories +// in a filesystem. +package noder + +import "fmt" + +// Hasher interface is implemented by types that can tell you +// their hash. +type Hasher interface { + Hash() []byte +} + +// The Noder interface is implemented by the elements of a Merkle Trie. +// +// There are two types of elements in a Merkle Trie: +// +// - file-like nodes: they cannot have children. +// +// - directory-like nodes: they can have 0 or more children and their +// hash is calculated by combining their children hashes. +type Noder interface { + Hasher + fmt.Stringer // for testing purposes + // Name returns the name of an element (relative, not its full + // path). + Name() string + // IsDir returns true if the element is a directory-like node or + // false if it is a file-like node. + IsDir() bool + // Children returns the children of the element. Note that empty + // directory-like noders and file-like noders will both return + // NoChildren. + Children() ([]Noder, error) + // NumChildren returns the number of children this element has. + // + // This method is an optimization: the number of children is easily + // calculated as the length of the value returned by the Children + // method (above); yet, some implementations will be able to + // implement NumChildren in O(1) while Children is usually more + // complex. + NumChildren() (int, error) + Skip() bool + + Equal(other Noder) bool +} + +// NoChildren represents the children of a noder without children. +var NoChildren = []Noder{} diff --git a/versionmgr/merkletrie/noder/noder_test.go b/versionmgr/merkletrie/noder/noder_test.go new file mode 100644 index 00000000..08c78449 --- /dev/null +++ b/versionmgr/merkletrie/noder/noder_test.go @@ -0,0 +1,90 @@ +package noder + +import ( + "bytes" + "testing" + + . "gopkg.in/check.v1" //nolint +) + +func Test(t *testing.T) { TestingT(t) } + +type NoderSuite struct{} + +var _ = Suite(&NoderSuite{}) + +type noderMock struct { + name string + hash []byte + isDir bool + children []Noder +} + +func (n noderMock) String() string { return n.Name() } +func (n noderMock) Hash() []byte { return n.hash } +func (n noderMock) Equal(other Noder) bool { return bytes.Equal(n.hash, other.Hash()) } +func (n noderMock) Name() string { return n.name } +func (n noderMock) IsDir() bool { return n.isDir } +func (n noderMock) Children() ([]Noder, error) { return n.children, nil } +func (n noderMock) NumChildren() (int, error) { return len(n.children), nil } +func (n noderMock) Skip() bool { return false } + +// Returns a sequence with the noders 3, 2, and 1 from the +// following diagram: +// +// 3 +// | +// 2 +// | +// 1 +// / \ +// +// c1 c2 +// +// This is also the path of "1". +func nodersFixture() []Noder { + n1 := &noderMock{ + name: "1", + hash: []byte{0x00, 0x01, 0x02}, + isDir: true, + children: childrenFixture(), + } + n2 := &noderMock{name: "2"} + n3 := &noderMock{name: "3"} + return []Noder{n3, n2, n1} +} + +// Returns a collection of 2 noders: c1, c2. +func childrenFixture() []Noder { + c1 := &noderMock{name: "c1"} + c2 := &noderMock{name: "c2"} + return []Noder{c1, c2} +} + +// returns nodersFixture as the path of "1". +func pathFixture() Path { + return Path(nodersFixture()) +} + +func (s *NoderSuite) TestString(c *C) { + c.Assert(pathFixture().String(), Equals, "3/2/1") +} + +func (s *NoderSuite) TestLast(c *C) { + c.Assert(pathFixture().Last().Name(), Equals, "1") +} + +func (s *NoderSuite) TestPathImplementsNoder(c *C) { + p := pathFixture() + c.Assert(p.Name(), Equals, "1") + c.Assert(p.Hash(), DeepEquals, []byte{0x00, 0x01, 0x02}) + c.Assert(p.IsDir(), Equals, true) + + children, err := p.Children() + c.Assert(err, IsNil) + c.Assert(children, DeepEquals, childrenFixture()) + + numChildren, err := p.NumChildren() + c.Assert(err, IsNil) + c.Assert(numChildren, Equals, 2) +} diff --git a/versionmgr/merkletrie/noder/path.go b/versionmgr/merkletrie/noder/path.go new file mode 100644 index 00000000..6c1d3633 --- /dev/null +++ b/versionmgr/merkletrie/noder/path.go @@ -0,0 +1,98 @@ +package noder + +import ( + "bytes" + "strings" +) + +// Path values represent a noder and its ancestors. The root goes first +// and the actual final noder the path is referring to will be the last. +// +// A path implements the Noder interface, redirecting all the interface +// calls to its final noder. +// +// Paths build from an empty Noder slice are not valid paths and should +// not be used. +type Path []Noder + +func (p Path) Skip() bool { + if len(p) > 0 { + return p.Last().Skip() + } + + return false +} + +// String returns the full path of the final noder as a string, using +// "/" as the separator. +func (p Path) String() string { + var buf bytes.Buffer + sep := "" + for _, e := range p { + _, _ = buf.WriteString(sep) + sep = "/" + _, _ = buf.WriteString(e.Name()) + } + + return buf.String() +} + +// Last returns the final noder in the path. +func (p Path) Last() Noder { + return p[len(p)-1] +} + +// Hash returns the hash of the final noder of the path. +func (p Path) Hash() []byte { + return p.Last().Hash() +} + +// Name returns the name of the final noder of the path. +func (p Path) Name() string { + return p.Last().Name() +} + +// IsDir returns if the final noder of the path is a directory-like +// noder. +func (p Path) IsDir() bool { + return p.Last().IsDir() +} + +// Children returns the children of the final noder in the path. +func (p Path) Children() ([]Noder, error) { + return p.Last().Children() +} + +// NumChildren returns the number of children the final noder of the +// path has. +func (p Path) NumChildren() (int, error) { + return p.Last().NumChildren() +} + +// Compare returns -1, 0 or 1 if the path p is smaller, equal or bigger +// than other, in "directory order"; for example: +// +// "a" < "b" +// "a/b/c/d/z" < "b" +// "a/b/a" > "a/b" +func (p Path) Compare(other Path) int { + i := 0 + for { + switch { + case len(other) == len(p) && i == len(p): + return 0 + case i == len(other): + return 1 + case i == len(p): + return -1 + default: + // We do *not* normalize Unicode here. CGit doesn't. + // https://github.com/src-d/go-git/issues/1057 + cmp := strings.Compare(p[i].Name(), other[i].Name()) + if cmp != 0 { + return cmp + } + } + i++ + } +} diff --git a/versionmgr/merkletrie/noder/path_test.go b/versionmgr/merkletrie/noder/path_test.go new file mode 100644 index 00000000..bf1fc4e8 --- /dev/null +++ b/versionmgr/merkletrie/noder/path_test.go @@ -0,0 +1,165 @@ +package noder + +import ( + "golang.org/x/text/unicode/norm" + . "gopkg.in/check.v1" //nolint +) + +type PathSuite struct{} + +var _ = Suite(&PathSuite{}) + +func (s *PathSuite) TestShortFile(c *C) { + f := &noderMock{ + name: "1", + isDir: false, + } + p := Path([]Noder{f}) + c.Assert(p.String(), Equals, "1") +} + +func (s *PathSuite) TestShortDir(c *C) { + d := &noderMock{ + name: "1", + isDir: true, + children: NoChildren, + } + p := Path([]Noder{d}) + c.Assert(p.String(), Equals, "1") +} + +func (s *PathSuite) TestLongFile(c *C) { + n3 := &noderMock{ + name: "3", + isDir: false, + } + n2 := &noderMock{ + name: "2", + isDir: true, + children: []Noder{n3}, + } + n1 := &noderMock{ + name: "1", + isDir: true, + children: []Noder{n2}, + } + p := Path([]Noder{n1, n2, n3}) + c.Assert(p.String(), Equals, "1/2/3") +} + +func (s *PathSuite) TestLongDir(c *C) { + n3 := &noderMock{ + name: "3", + isDir: true, + children: NoChildren, + } + n2 := &noderMock{ + name: "2", + isDir: true, + children: []Noder{n3}, + } + n1 := &noderMock{ + name: "1", + isDir: true, + children: []Noder{n2}, + } + p := Path([]Noder{n1, n2, n3}) + c.Assert(p.String(), Equals, "1/2/3") +} + +func (s *PathSuite) TestCompareDepth1(c *C) { + p1 := Path([]Noder{&noderMock{name: "a"}}) + p2 := Path([]Noder{&noderMock{name: "b"}}) + c.Assert(p1.Compare(p2), Equals, -1) + c.Assert(p2.Compare(p1), Equals, 1) + + p1 = Path([]Noder{&noderMock{name: "a"}}) + p2 = Path([]Noder{&noderMock{name: "a"}}) + c.Assert(p1.Compare(p2), Equals, 0) + c.Assert(p2.Compare(p1), Equals, 0) + + p1 = Path([]Noder{&noderMock{name: "a.go"}}) + p2 = Path([]Noder{&noderMock{name: "a"}}) + c.Assert(p1.Compare(p2), Equals, 1) + c.Assert(p2.Compare(p1), Equals, -1) +} + +func (s *PathSuite) TestCompareDepth2(c *C) { + p1 := Path([]Noder{ + &noderMock{name: "a"}, + &noderMock{name: "b"}, + }) + p2 := Path([]Noder{ + &noderMock{name: "b"}, + &noderMock{name: "a"}, + }) + c.Assert(p1.Compare(p2), Equals, -1) + c.Assert(p2.Compare(p1), Equals, 1) + + p1 = Path([]Noder{ + &noderMock{name: "a"}, + &noderMock{name: "b"}, + }) + p2 = Path([]Noder{ + &noderMock{name: "a"}, + &noderMock{name: "b"}, + }) + c.Assert(p1.Compare(p2), Equals, 0) + c.Assert(p2.Compare(p1), Equals, 0) + + p1 = Path([]Noder{ + &noderMock{name: "a"}, + &noderMock{name: "b"}, + }) + p2 = Path([]Noder{ + &noderMock{name: "a"}, + &noderMock{name: "a"}, + }) + c.Assert(p1.Compare(p2), Equals, 1) + c.Assert(p2.Compare(p1), Equals, -1) +} + +func (s *PathSuite) TestCompareMixedDepths(c *C) { + p1 := Path([]Noder{ + &noderMock{name: "a"}, + &noderMock{name: "b"}, + }) + p2 := Path([]Noder{&noderMock{name: "b"}}) + c.Assert(p1.Compare(p2), Equals, -1) + c.Assert(p2.Compare(p1), Equals, 1) + + p1 = Path([]Noder{ + &noderMock{name: "b"}, + &noderMock{name: "b"}, + }) + p2 = Path([]Noder{&noderMock{name: "b"}}) + c.Assert(p1.Compare(p2), Equals, 1) + c.Assert(p2.Compare(p1), Equals, -1) + + p1 = Path([]Noder{&noderMock{name: "a.go"}}) + p2 = Path([]Noder{ + &noderMock{name: "a"}, + &noderMock{name: "a.go"}, + }) + c.Assert(p1.Compare(p2), Equals, 1) + c.Assert(p2.Compare(p1), Equals, -1) + + p1 = Path([]Noder{&noderMock{name: "b.go"}}) + p2 = Path([]Noder{ + &noderMock{name: "a"}, + &noderMock{name: "a.go"}, + }) + c.Assert(p1.Compare(p2), Equals, 1) + c.Assert(p2.Compare(p1), Equals, -1) +} + +func (s *PathSuite) TestCompareNormalization(c *C) { + p1 := Path([]Noder{&noderMock{name: norm.NFKC.String("페")}}) + p2 := Path([]Noder{&noderMock{name: norm.NFKD.String("페")}}) + c.Assert(p1.Compare(p2), Equals, 1) + c.Assert(p2.Compare(p1), Equals, -1) + p1 = Path([]Noder{&noderMock{name: "TestAppWithUnicodéPath"}}) + p2 = Path([]Noder{&noderMock{name: "TestAppWithUnicodéPath"}}) + c.Assert(p1.Compare(p2), Equals, -1) + c.Assert(p2.Compare(p1), Equals, 1) +} diff --git a/versionmgr/tree_node.go b/versionmgr/tree_node.go index d2c786cb..2e952bb1 100644 --- a/versionmgr/tree_node.go +++ b/versionmgr/tree_node.go @@ -1,12 +1,12 @@ package versionmgr import ( + "bytes" "context" "fmt" - "github.com/go-git/go-git/v5/utils/merkletrie/noder" "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/models/filemode" + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" ) var _ noder.Noder = (*TreeNode)(nil) @@ -15,10 +15,10 @@ type TreeNode struct { ctx context.Context entry models.TreeEntry treeNode *models.TreeNode - objectRepo models.IObjectRepo + objectRepo models.IFileTreeRepo } -func NewTreeNode(ctx context.Context, entry models.TreeEntry, object models.IObjectRepo) (*TreeNode, error) { +func NewTreeNode(ctx context.Context, entry models.TreeEntry, object models.IFileTreeRepo) (*TreeNode, error) { treeNode := EmptyRoot if !entry.Equal(EmptyDirEntry) { var err error @@ -34,6 +34,11 @@ func NewTreeNode(ctx context.Context, entry models.TreeEntry, object models.IObj func (n TreeNode) Type() models.ObjectType { return n.treeNode.Type } + +func (n TreeNode) Properties() models.Property { + return n.treeNode.Properties +} + func (n TreeNode) SubObjects() []models.TreeEntry { return n.treeNode.SubObjects } @@ -42,10 +47,14 @@ func (n TreeNode) TreeNode() *models.TreeNode { return n.treeNode } +func (n TreeNode) Equal(other noder.Noder) bool { + return bytes.Equal(n.Hash(), other.Hash()) +} + func (n TreeNode) SubDir(ctx context.Context, name string) (*TreeNode, error) { for _, node := range n.treeNode.SubObjects { if node.Name == name { - if node.Mode == filemode.Dir { + if node.IsDir { return NewTreeNode(ctx, node, n.objectRepo) } return nil, fmt.Errorf("node is not directory") @@ -57,7 +66,7 @@ func (n TreeNode) SubDir(ctx context.Context, name string) (*TreeNode, error) { func (n TreeNode) SubFile(ctx context.Context, name string) (*models.Blob, error) { for _, node := range n.treeNode.SubObjects { if node.Name == name { - if node.Mode == filemode.Regular || node.Mode == filemode.Executable { + if !node.IsDir { return n.objectRepo.Blob(ctx, node.Hash) } return nil, fmt.Errorf("node is not blob") @@ -88,7 +97,7 @@ func (n TreeNode) Name() string { } func (n TreeNode) IsDir() bool { - return n.entry.Mode == filemode.Dir + return n.entry.IsDir } func (n TreeNode) Children() ([]noder.Noder, error) { diff --git a/versionmgr/worktree.go b/versionmgr/worktree.go index 8c54fc2b..87e8524b 100644 --- a/versionmgr/worktree.go +++ b/versionmgr/worktree.go @@ -1,7 +1,6 @@ package versionmgr import ( - "bytes" "context" "errors" "fmt" @@ -9,13 +8,10 @@ import ( "os" "path/filepath" "strings" - "time" - - "github.com/go-git/go-git/v5/utils/merkletrie/noder" "github.com/jiaozifs/jiaozifs/utils/httputil" - "github.com/go-git/go-git/v5/utils/merkletrie" + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" "github.com/jiaozifs/jiaozifs/block" "github.com/jiaozifs/jiaozifs/utils/hash" @@ -35,7 +31,6 @@ var EmptyRoot = &models.TreeNode{ var EmptyDirEntry = models.TreeEntry{ Name: "", Hash: hash.Hash([]byte{}), - Mode: filemode.Dir, } var ( @@ -46,23 +41,23 @@ var ( ) type FullObject struct { - node *models.Object + node *models.FileTree entry models.TreeEntry } func (objectName FullObject) Entry() models.TreeEntry { return objectName.entry } -func (objectName FullObject) Node() *models.Object { +func (objectName FullObject) Node() *models.FileTree { return objectName.node } type WorkTree struct { - object models.IObjectRepo + object models.IFileTreeRepo root *TreeNode } -func NewWorkTree(ctx context.Context, object models.IObjectRepo, root models.TreeEntry) (*WorkTree, error) { +func NewWorkTree(ctx context.Context, object models.IFileTreeRepo, root models.TreeEntry) (*WorkTree, error) { rootNode, err := NewTreeNode(ctx, root, object) if err != nil { return nil, err @@ -79,7 +74,7 @@ func (workTree *WorkTree) Root() *TreeNode { // ReadBlob read blob content with range func (workTree *WorkTree) ReadBlob(ctx context.Context, adapter block.Adapter, blob *models.Blob, rangeSpec *string) (io.ReadCloser, error) { - address := pathutil.PathOfHash(blob.Hash) + address := pathutil.PathOfHash(blob.CheckSum) pointer := block.ObjectPointer{ StorageNamespace: adapter.BlockstoreType() + "://", IdentifierType: block.IdentifierTypeRelative, @@ -111,7 +106,7 @@ func (workTree *WorkTree) ReadBlob(ctx context.Context, adapter block.Adapter, b } // WriteBlob write blob content to storage -func (workTree *WorkTree) WriteBlob(ctx context.Context, adapter block.Adapter, body io.Reader, contentLength int64, opts block.PutOpts) (*models.Blob, error) { +func (workTree *WorkTree) WriteBlob(ctx context.Context, adapter block.Adapter, body io.Reader, contentLength int64, properties models.Property) (*models.Blob, error) { // handle the upload itself hashReader := hash.NewHashingReader(body, hash.Md5) tempf, err := os.CreateTemp("", "*") @@ -123,7 +118,7 @@ func (workTree *WorkTree) WriteBlob(ctx context.Context, adapter block.Adapter, return nil, err } - hash := hash.Hash(hashReader.Md5.Sum(nil)) + checkSum := hash.Hash(hashReader.Md5.Sum(nil)) _, err = tempf.Seek(0, io.SeekStart) if err != nil { return nil, err @@ -135,21 +130,17 @@ func (workTree *WorkTree) WriteBlob(ctx context.Context, adapter block.Adapter, _ = os.RemoveAll(name) }() - address := pathutil.PathOfHash(hash) + address := pathutil.PathOfHash(checkSum) err = adapter.Put(ctx, block.ObjectPointer{ StorageNamespace: adapter.BlockstoreType() + "://", IdentifierType: block.IdentifierTypeRelative, Identifier: address, - }, contentLength, tempf, opts) + }, contentLength, tempf, block.PutOpts{}) if err != nil { return nil, err } - return &models.Blob{ - Type: models.BlobObject, - Hash: hash, - Size: hashReader.CopiedSize, - }, nil + return models.NewBlob(properties, checkSum, hashReader.CopiedSize) } func (workTree *WorkTree) AppendDirectEntry(ctx context.Context, treeEntry models.TreeEntry) (*models.TreeNode, error) { @@ -163,20 +154,14 @@ func (workTree *WorkTree) AppendDirectEntry(ctx context.Context, treeEntry model } } - newTree := &models.TreeNode{ - Type: workTree.root.Type(), - SubObjects: workTree.root.SubObjects(), - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - newTree.SubObjects = append(newTree.SubObjects, treeEntry) - hash, err := newTree.GetHash() + subObjects := models.SortSubObjects(append(workTree.root.SubObjects(), treeEntry)) + + newTree, err := models.NewTreeNode(models.Property{Mode: filemode.Dir}, subObjects...) if err != nil { return nil, err } - newTree.Hash = hash - obj, err := workTree.object.Insert(ctx, newTree.Object()) + obj, err := workTree.object.Insert(ctx, newTree.FileTree()) if err != nil { return nil, err } @@ -184,29 +169,24 @@ func (workTree *WorkTree) AppendDirectEntry(ctx context.Context, treeEntry model } func (workTree *WorkTree) DeleteDirectEntry(ctx context.Context, name string) (*models.TreeNode, bool, error) { - newTree := &models.TreeNode{ - Type: workTree.root.Type(), - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } + var subObjects []models.TreeEntry for _, sub := range workTree.root.SubObjects() { if sub.Name != name { //filter tree entry by name - newTree.SubObjects = append(newTree.SubObjects, sub) + subObjects = append(subObjects, sub) } } - if len(newTree.SubObjects) == 0 { + if len(subObjects) == 0 { //this node has no element return nothing return nil, true, nil } - hash, err := newTree.GetHash() + newTree, err := models.NewTreeNode(workTree.root.Properties(), subObjects...) if err != nil { return nil, false, err } - newTree.Hash = hash - obj, err := workTree.object.Insert(ctx, newTree.Object()) + obj, err := workTree.object.Insert(ctx, newTree.FileTree()) if err != nil { return nil, false, err } @@ -225,22 +205,16 @@ func (workTree *WorkTree) ReplaceSubTreeEntry(ctx context.Context, treeEntry mod return nil, ErrPathNotFound } - newTree := &models.TreeNode{ - Type: workTree.root.Type(), - SubObjects: make([]models.TreeEntry, len(workTree.root.SubObjects())), - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - copy(newTree.SubObjects, workTree.root.SubObjects()) - newTree.SubObjects[index] = treeEntry + subObjects := make([]models.TreeEntry, len(workTree.root.SubObjects())) + copy(subObjects, workTree.root.SubObjects()) + subObjects[index] = treeEntry - hash, err := newTree.GetHash() + newTree, err := models.NewTreeNode(workTree.Root().Properties(), subObjects...) if err != nil { return nil, err } - newTree.Hash = hash - obj, err := workTree.object.Insert(ctx, newTree.Object()) + obj, err := workTree.object.Insert(ctx, newTree.FileTree()) if err != nil { return nil, err } @@ -263,13 +237,13 @@ func (workTree *WorkTree) MatchPath(ctx context.Context, path string) ([]FullObj return existNodes, missingPath, nil } - if entry.Mode == filemode.Dir { + if entry.IsDir { curNode, err = curNode.SubDir(ctx, entry.Name) if err != nil { return nil, nil, err } existNodes = append(existNodes, FullObject{ - node: curNode.TreeNode().Object(), + node: curNode.TreeNode().FileTree(), entry: entry, }) } else { @@ -279,7 +253,7 @@ func (workTree *WorkTree) MatchPath(ctx context.Context, path string) ([]FullObj return nil, nil, err } existNodes = append(existNodes, FullObject{ - node: blob.Object(), + node: blob.FileTree(), entry: entry, }) @@ -304,7 +278,7 @@ func (workTree *WorkTree) AddLeaf(ctx context.Context, fullPath string, blob *mo return ErrEntryExit } - _, err = workTree.object.Insert(ctx, blob.Object()) + _, err = workTree.object.Insert(ctx, blob.FileTree()) if err != nil { return err } @@ -313,36 +287,36 @@ func (workTree *WorkTree) AddLeaf(ctx context.Context, fullPath string, blob *mo var lastEntry models.TreeEntry for index, path := range missingPath { if index == 0 { - _, err = workTree.object.Insert(ctx, blob.Object()) + _, err = workTree.object.Insert(ctx, blob.FileTree()) if err != nil { return err } lastEntry = models.TreeEntry{ - Name: path, - Mode: filemode.Regular, - Hash: blob.Hash, + Name: path, + IsDir: false, + Hash: blob.Hash, } continue } - newTree, err := models.NewTreeNode(lastEntry) + newTree, err := models.NewTreeNode(models.DefaultDirProperty(), lastEntry) if err != nil { return err } - _, err = workTree.object.Insert(ctx, newTree.Object()) + _, err = workTree.object.Insert(ctx, newTree.FileTree()) if err != nil { return err } lastEntry = models.TreeEntry{ - Name: path, - Mode: filemode.Dir, - Hash: newTree.Hash, + Name: path, + IsDir: true, + Hash: newTree.Hash, } } slices.Reverse(existNode) existNode = append(existNode, FullObject{ - node: workTree.root.TreeNode().Object(), + node: workTree.root.TreeNode().FileTree(), entry: models.NewRootTreeEntry(workTree.root.Hash()), //root node have no name }) @@ -355,16 +329,15 @@ func (workTree *WorkTree) AddLeaf(ctx context.Context, fullPath string, blob *mo if index == 0 { //insert new node newNode, err = newWorkTree.AppendDirectEntry(ctx, lastEntry) } else { //replace node - newNode, err = newWorkTree.ReplaceSubTreeEntry(ctx, lastEntry) } if err != nil { return err } lastEntry = models.TreeEntry{ - Name: node.Entry().Name, // use old name but replace with new hase - Mode: node.Entry().Mode, - Hash: newNode.Hash, + Name: node.Entry().Name, // use old name but replace with new hase + IsDir: true, + Hash: newNode.Hash, } } workTree.root, err = NewTreeNode(ctx, lastEntry, workTree.object) @@ -382,14 +355,14 @@ func (workTree *WorkTree) ReplaceLeaf(ctx context.Context, fullPath string, blob return ErrPathNotFound } - _, err = workTree.object.Insert(ctx, blob.Object()) + _, err = workTree.object.Insert(ctx, blob.FileTree()) if err != nil { return err } slices.Reverse(existNode) existNode = append(existNode, FullObject{ - node: workTree.root.TreeNode().Object(), + node: workTree.root.TreeNode().FileTree(), entry: models.NewRootTreeEntry(workTree.root.Hash()), //root node have no name }) @@ -398,9 +371,9 @@ func (workTree *WorkTree) ReplaceLeaf(ctx context.Context, fullPath string, blob for index, node := range existNode { if index == 0 { lastEntry = models.TreeEntry{ - Name: node.Entry().Name, - Mode: node.Entry().Mode, - Hash: blob.Hash, + Name: node.Entry().Name, + IsDir: false, + Hash: blob.Hash, } continue } @@ -414,9 +387,9 @@ func (workTree *WorkTree) ReplaceLeaf(ctx context.Context, fullPath string, blob return err } lastEntry = models.TreeEntry{ - Name: node.Entry().Name, - Mode: node.Entry().Mode, - Hash: newNode.Hash, + Name: node.Entry().Name, + IsDir: true, + Hash: newNode.Hash, } } workTree.root, err = NewTreeNode(ctx, lastEntry, workTree.object) @@ -446,7 +419,7 @@ func (workTree *WorkTree) RemoveEntry(ctx context.Context, fullPath string) erro slices.Reverse(existNode) existNode = append(existNode, FullObject{ - node: workTree.root.TreeNode().Object(), + node: workTree.root.TreeNode().FileTree(), entry: models.NewRootTreeEntry(workTree.root.Hash()), //root node have no name }) @@ -467,8 +440,8 @@ func (workTree *WorkTree) RemoveEntry(ctx context.Context, fullPath string) erro } lastEntry = models.TreeEntry{ - Name: node.Entry().Name, - Mode: node.Entry().Mode, + Name: node.Entry().Name, + IsDir: true, //leaf node has skip before loop,exist node must be directory } if !isEmpty { lastEntry.Hash = newNode.Hash @@ -479,9 +452,9 @@ func (workTree *WorkTree) RemoveEntry(ctx context.Context, fullPath string) erro return err } lastEntry = models.TreeEntry{ - Name: node.Entry().Name, - Mode: node.Entry().Mode, - Hash: newNode.Hash, + Name: node.Entry().Name, + IsDir: true, + Hash: newNode.Hash, } } } @@ -574,9 +547,7 @@ func (workTree *WorkTree) Diff(ctx context.Context, rootTreeHash hash.Hash) (*Ch return nil, err } - changes, err := merkletrie.DiffTreeContext(ctx, workTree.Root(), toNode, func(a, b noder.Hasher) bool { - return bytes.Equal(a.Hash(), b.Hash()) - }) + changes, err := merkletrie.DiffTreeContext(ctx, workTree.Root(), toNode) if err != nil { return nil, err } diff --git a/versionmgr/worktree_test.go b/versionmgr/worktree_test.go index 3def9149..247b9857 100644 --- a/versionmgr/worktree_test.go +++ b/versionmgr/worktree_test.go @@ -12,21 +12,19 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/jiaozifs/jiaozifs/block" - "github.com/jiaozifs/jiaozifs/block/mem" "github.com/jiaozifs/jiaozifs/models" "github.com/jiaozifs/jiaozifs/testhelper" ) -func TestTreeOpWriteBlob(t *testing.T) { +func TestTreeWriteBlob(t *testing.T) { ctx := context.Background() postgres, _, db := testhelper.SetupDatabase(ctx, t) defer postgres.Stop() //nolint adapter := mem.New(ctx) - objRepo := models.NewObjectRepo(db) + objRepo := models.NewFileTree(db) workTree, err := NewWorkTree(ctx, objRepo, EmptyDirEntry) require.NoError(t, err) @@ -34,10 +32,11 @@ func TestTreeOpWriteBlob(t *testing.T) { binary := []byte("Build simple, secure, scalable systems with Go") bLen := int64(len(binary)) r := bytes.NewReader(binary) - blob, err := workTree.WriteBlob(ctx, adapter, r, bLen, block.PutOpts{}) + blob, err := workTree.WriteBlob(ctx, adapter, r, bLen, models.DefaultLeafProperty()) require.NoError(t, err) assert.Equal(t, bLen, blob.Size) - assert.Equal(t, "f3b39786b86a96372589aa1166966643", blob.Hash.Hex()) + assert.Equal(t, "99b91d4c517d0cded9506be9298b8d02", blob.Hash.Hex()) + assert.Equal(t, "f3b39786b86a96372589aa1166966643", blob.CheckSum.Hex()) reader, err := workTree.ReadBlob(ctx, adapter, blob, nil) require.NoError(t, err) @@ -46,13 +45,13 @@ func TestTreeOpWriteBlob(t *testing.T) { require.Equal(t, binary, content) } -func TestTreeOpTreeOp(t *testing.T) { +func TestWorkTreeTreeOp(t *testing.T) { ctx := context.Background() postgres, _, db := testhelper.SetupDatabase(ctx, t) defer postgres.Stop() //nolint adapter := mem.New(ctx) - objRepo := models.NewObjectRepo(db) + objRepo := models.NewFileTree(db) workTree, err := NewWorkTree(ctx, objRepo, EmptyDirEntry) require.NoError(t, err) @@ -60,12 +59,12 @@ func TestTreeOpTreeOp(t *testing.T) { binary := []byte("Build simple, secure, scalable systems with Go") bLen := int64(len(binary)) r := bytes.NewReader(binary) - blob, err := workTree.WriteBlob(ctx, adapter, r, bLen, block.PutOpts{}) + blob, err := workTree.WriteBlob(ctx, adapter, r, bLen, models.DefaultLeafProperty()) require.NoError(t, err) err = workTree.AddLeaf(ctx, "a/b/c.txt", blob) require.NoError(t, err) - require.Equal(t, "3bf643c30934d121ee45d413b165f135", hash.Hash(workTree.Root().Hash()).Hex()) + require.Equal(t, "faf499deee898c13e4ae4a2e6c4230fb", hash.Hash(workTree.Root().Hash()).Hex()) //add again expect get an error err = workTree.AddLeaf(ctx, "a/b/c.txt", blob) @@ -75,12 +74,12 @@ func TestTreeOpTreeOp(t *testing.T) { binary = []byte(`“At the time, no single team member knew Go, but within a month, everyone was writing in Go and we were building out the endpoints. ”`) bLen = int64(len(binary)) r = bytes.NewReader(binary) - blob, err = workTree.WriteBlob(ctx, adapter, r, bLen, block.PutOpts{}) + blob, err = workTree.WriteBlob(ctx, adapter, r, bLen, models.DefaultLeafProperty()) require.NoError(t, err) err = workTree.ReplaceLeaf(ctx, "a/b/c.txt", blob) require.NoError(t, err) - require.Equal(t, "8856b15f0f6c7ad21bfabe812df69e83", hash.Hash(workTree.Root().Hash()).Hex()) + require.Equal(t, "d08bf786f0b4375dd6edd880859dc47a", hash.Hash(workTree.Root().Hash()).Hex()) { //find blob @@ -93,7 +92,7 @@ func TestTreeOpTreeOp(t *testing.T) { //add another branch err = workTree.AddLeaf(ctx, "a/b/d.txt", blob) require.NoError(t, err) - require.Equal(t, "225f0ca6233681a441969922a7425db2", hash.Hash(workTree.Root().Hash()).Hex()) + require.Equal(t, "b37d803cc5431587ef6f6e4d3aa8ada4", hash.Hash(workTree.Root().Hash()).Hex()) } @@ -132,7 +131,7 @@ func TestTreeOpTreeOp(t *testing.T) { err = workTree.RemoveEntry(ctx, "a/b/c.txt") require.NoError(t, err) - require.Equal(t, "f90e2d306ad172824fa171b9e0d9e133", hash.Hash(workTree.Root().Hash()).Hex()) + require.Equal(t, "291af4419b76a09b60aa0cf911c72d06", hash.Hash(workTree.Root().Hash()).Hex()) err = workTree.RemoveEntry(ctx, "a/b/d.txt") require.NoError(t, err) @@ -145,34 +144,37 @@ func TestRemoveEntry(t *testing.T) { defer postgres.Stop() //nolint adapter := mem.New(ctx) - objRepo := models.NewObjectRepo(db) + objRepo := models.NewFileTree(db) - treeOp, err := NewWorkTree(ctx, objRepo, EmptyDirEntry) + workTree, err := NewWorkTree(ctx, objRepo, EmptyDirEntry) require.NoError(t, err) binary := []byte("Build simple, secure, scalable systems with Go") bLen := int64(len(binary)) r := bytes.NewReader(binary) - blob, err := treeOp.WriteBlob(ctx, adapter, r, bLen, block.PutOpts{}) + blob, err := workTree.WriteBlob(ctx, adapter, r, bLen, models.DefaultLeafProperty()) require.NoError(t, err) - err = treeOp.AddLeaf(ctx, "a/b/c.txt", blob) + err = workTree.AddLeaf(ctx, "a/b/c.txt", blob) require.NoError(t, err) - require.Equal(t, "3bf643c30934d121ee45d413b165f135", hash.Hash(treeOp.Root().Hash()).Hex()) + require.Equal(t, "faf499deee898c13e4ae4a2e6c4230fb", hash.Hash(workTree.Root().Hash()).Hex()) //update path binary = []byte(`“At the time, no single team member knew Go, but within a month, everyone was writing in Go and we were building out the endpoints. ”`) bLen = int64(len(binary)) r = bytes.NewReader(binary) - blob, err = treeOp.WriteBlob(ctx, adapter, r, bLen, block.PutOpts{}) + blob, err = workTree.WriteBlob(ctx, adapter, r, bLen, models.DefaultLeafProperty()) require.NoError(t, err) //add another branch - err = treeOp.AddLeaf(ctx, "a/b/d.txt", blob) + err = workTree.AddLeaf(ctx, "a/b/d.txt", blob) require.NoError(t, err) - require.Equal(t, "81173b4a85cc5643feacd38b975e61a1", hash.Hash(treeOp.Root().Hash()).Hex()) + require.Equal(t, "77e60b4b1f28022818a3b97dfe064a3e", hash.Hash(workTree.Root().Hash()).Hex()) - err = treeOp.RemoveEntry(ctx, "a/b") + err = workTree.RemoveEntry(ctx, "a/b") + require.NoError(t, err) + require.Equal(t, "", hash.Hash(workTree.Root().Hash()).Hex()) + entries, err := workTree.Ls(ctx, "") require.NoError(t, err) - require.Equal(t, "", hash.Hash(treeOp.Root().Hash()).Hex()) + require.Len(t, entries, 0) } From 0cef9418bb891f2997e32964582775393a7c2665 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Mon, 18 Dec 2023 10:00:04 +0800 Subject: [PATCH 081/210] fix: missing secret store --- api/api_impl/server.go | 6 ++++-- auth/session_store.go | 9 ++++++--- cmd/daemon.go | 3 +++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/api/api_impl/server.go b/api/api_impl/server.go index 08121950..5a032bce 100644 --- a/api/api_impl/server.go +++ b/api/api_impl/server.go @@ -6,6 +6,8 @@ import ( "net" "net/http" + "github.com/jiaozifs/jiaozifs/auth/crypt" + "github.com/MadAppGang/httplog" "github.com/rs/cors" @@ -32,7 +34,7 @@ var log = logging.Logger("rpc") const APIV1Prefix = "/api/v1" -func SetupAPI(lc fx.Lifecycle, apiConfig *config.APIConfig, sessionStore sessions.Store, repo models.IRepo, controller APIController) error { +func SetupAPI(lc fx.Lifecycle, apiConfig *config.APIConfig, secretStore crypt.SecretStore, sessionStore sessions.Store, repo models.IRepo, controller APIController) error { swagger, err := api.GetSwagger() if err != nil { return err @@ -64,7 +66,7 @@ func SetupAPI(lc fx.Lifecycle, apiConfig *config.APIConfig, sessionStore session }, SilenceServersWarning: true, }), - auth.Middleware(swagger, nil, nil, repo.UserRepo(), sessionStore), + auth.Middleware(swagger, nil, secretStore, repo.UserRepo(), sessionStore), ) raw, err := api.RawSpec() diff --git a/auth/session_store.go b/auth/session_store.go index 3108c62a..e15f2ab9 100644 --- a/auth/session_store.go +++ b/auth/session_store.go @@ -6,7 +6,10 @@ import ( "github.com/jiaozifs/jiaozifs/config" ) -func NewSessionStore(authConfig *config.AuthConfig) sessions.Store { - sstore := crypt.NewSecretStore(authConfig.SecretKey) - return sessions.NewCookieStore(sstore.SharedSecret()) +func NewSessionStore(secretStrore crypt.SecretStore) sessions.Store { + return sessions.NewCookieStore(secretStrore.SharedSecret()) +} + +func NewSectetStore(authConfig *config.AuthConfig) crypt.SecretStore { + return crypt.NewSecretStore(authConfig.SecretKey) } diff --git a/cmd/daemon.go b/cmd/daemon.go index 305be222..cc105aad 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -3,6 +3,8 @@ package cmd import ( "context" + "github.com/jiaozifs/jiaozifs/auth/crypt" + "github.com/jiaozifs/jiaozifs/version" "github.com/gorilla/sessions" @@ -65,6 +67,7 @@ var daemonCmd = &cobra.Command{ fx_opt.Override(fx_opt.NextInvoke(), migrations.MigrateDatabase), //api + fx_opt.Override(new(crypt.SecretStore), auth.NewSectetStore), fx_opt.Override(new(sessions.Store), auth.NewSessionStore), fx_opt.Override(fx_opt.NextInvoke(), apiImpl.SetupAPI), ) From e3bb650ded0912eab75c64a5ed28df037176ae5e Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Mon, 18 Dec 2023 12:38:21 +0800 Subject: [PATCH 082/210] fix: remove get usr info from token --- auth/auth_test.go | 5 ----- auth/basic_auth.go | 46 ------------------------------------------ controller/user_ctl.go | 24 ++++++++++++---------- 3 files changed, 13 insertions(+), 62 deletions(-) diff --git a/auth/auth_test.go b/auth/auth_test.go index 89bb1a33..b1581c74 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -42,9 +42,4 @@ func TestLogin_Success(t *testing.T) { require.NoError(t, err, "Login should not return an error") require.NotEmpty(t, token.Token, "Token should not be empty") require.NotNil(t, token.TokenExpiration, "Token expiration should not be nil") - // profile - userInfo := &UserInfo{Token: token.Token} - profile, err := userInfo.UserProfile(ctx, mockRepo, mockConfig) - require.NoError(t, err) - require.NotEmpty(t, profile) } diff --git a/auth/basic_auth.go b/auth/basic_auth.go index 2eafec14..ea047a38 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -7,9 +7,6 @@ import ( "github.com/jiaozifs/jiaozifs/config" - "github.com/golang-jwt/jwt" - openapitypes "github.com/oapi-codegen/runtime/types" - "github.com/go-openapi/swag" logging "github.com/ipfs/go-log/v2" "github.com/jiaozifs/jiaozifs/api" @@ -101,46 +98,3 @@ func (r *Register) Register(ctx context.Context, repo models.IUserRepo) error { log.Infof("%s registration success", insertUser.Name) return nil } - -type UserInfo struct { - Token string `json:"token"` -} - -func (u *UserInfo) UserProfile(ctx context.Context, repo models.IUserRepo, config *config.AuthConfig) (api.UserInfo, error) { - userInfo := api.UserInfo{} - // Parse JWT Token - token, err := jwt.Parse(u.Token, func(token *jwt.Token) (interface{}, error) { - return config.SecretKey, nil - }) - if err != nil { - return userInfo, fmt.Errorf("cannot parse token %s %w", token.Raw, err) - } - // check token validity - if !token.Valid { - return userInfo, fmt.Errorf("token %s invalid %w", token.Raw, ErrInvalidToken) - } - // Get username by token - claims, ok := token.Claims.(jwt.MapClaims) - if !ok { - return userInfo, ErrExtractClaims - } - username := claims["sub"].(string) - - // Get user by username - user, err := repo.Get(ctx, models.NewGetUserParams().SetName(username)) - if err != nil { - return userInfo, err - } - userInfo = api.UserInfo{ - CreatedAt: &user.CreatedAt, - CurrentSignInAt: &user.CurrentSignInAt, - CurrentSignInIP: &user.CurrentSignInIP, - Email: openapitypes.Email(user.Email), - LastSignInAt: &user.LastSignInAt, - LastSignInIP: &user.LastSignInIP, - UpdateAt: &user.UpdatedAt, - Username: user.Name, - } - log.Info("get user profile success") - return userInfo, nil -} diff --git a/controller/user_ctl.go b/controller/user_ctl.go index ee1ac5fa..961729e9 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -2,9 +2,10 @@ package controller import ( "context" - "fmt" "net/http" + openapitypes "github.com/oapi-codegen/runtime/types" + logging "github.com/ipfs/go-log/v2" "github.com/gorilla/sessions" @@ -71,25 +72,26 @@ func (userCtl UserController) Register(ctx context.Context, w *api.JiaozifsRespo w.OK() } -func (userCtl UserController) GetUserInfo(ctx context.Context, w *api.JiaozifsResponse, r *http.Request) { +func (userCtl UserController) GetUserInfo(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request) { // Get token from Header user, err := auth.GetUser(ctx) if err != nil { w.Error(err) return } - fmt.Println(user) - tokenString := r.Header.Get(AuthHeader) - userInfo := &auth.UserInfo{Token: tokenString} // perform GetUserInfo - usrInfo, err := userInfo.UserProfile(ctx, userCtl.Repo.UserRepo(), userCtl.Config) - if err != nil { - w.Error(err) - return + userInfo := api.UserInfo{ + CreatedAt: &user.CreatedAt, + CurrentSignInAt: &user.CurrentSignInAt, + CurrentSignInIP: &user.CurrentSignInIP, + Email: openapitypes.Email(user.Email), + LastSignInAt: &user.LastSignInAt, + LastSignInIP: &user.LastSignInIP, + UpdateAt: &user.UpdatedAt, + Username: user.Name, } - - w.JSON(usrInfo) + w.JSON(userInfo) } func (userCtl UserController) Logout(_ context.Context, w *api.JiaozifsResponse, r *http.Request) { From 9bf00d529ae7ef36d3188d3f3a0e037d9ffd4981 Mon Sep 17 00:00:00 2001 From: zjy Date: Mon, 18 Dec 2023 14:23:37 +0800 Subject: [PATCH 083/210] fix: fix authentication mechanism --- api/api_impl/server.go | 3 +-- api/jiaozifs.gen.go | 36 ++++++++++++------------------------ api/resp.gen.go | 2 +- api/swagger.yml | 36 +++++++++++++++++++++++++++++++++++- 4 files changed, 49 insertions(+), 28 deletions(-) diff --git a/api/api_impl/server.go b/api/api_impl/server.go index 5a032bce..10ab5b58 100644 --- a/api/api_impl/server.go +++ b/api/api_impl/server.go @@ -3,6 +3,7 @@ package apiimpl import ( "context" "errors" + "github.com/jiaozifs/jiaozifs/auth" "net" "net/http" @@ -17,8 +18,6 @@ import ( "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/auth" - "net/url" "github.com/getkin/kin-openapi/openapi3filter" diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index da755952..956a2b3c 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -5136,12 +5136,6 @@ func (siw *ServerInterfaceWrapper) Register(w http.ResponseWriter, r *http.Reque } } - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - - ctx = context.WithValue(ctx, Basic_authScopes, []string{}) - - ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.Register(r.Context(), &JiaozifsResponse{w}, r, body) })) @@ -5230,12 +5224,6 @@ func (siw *ServerInterfaceWrapper) GetUserInfo(w http.ResponseWriter, r *http.Re func (siw *ServerInterfaceWrapper) GetVersion(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - - ctx = context.WithValue(ctx, Basic_authScopes, []string{}) - - ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetVersion(r.Context(), &JiaozifsResponse{w}, r) })) @@ -6013,18 +6001,18 @@ var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/resp.gen.go b/api/resp.gen.go index b726ce38..0cfccc98 100644 --- a/api/resp.gen.go +++ b/api/resp.gen.go @@ -3,7 +3,7 @@ // // Generated by this command: // -// mockgen --package=api --destination=resp.gen.go net/http ResponseWriter +// mockgen.exe --package=api --destination=resp.gen.go net/http ResponseWriter // // Package api is a generated GoMock package. package api diff --git a/api/swagger.yml b/api/swagger.yml index 067ecea0..cd3020bb 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -14,10 +14,42 @@ servers: - url: "/api/v1" description: jiaozifs server endpoint security: - - jwt_token: [] + - jwt_token: [] # Default security for the entire API - basic_auth: [] - cookie_auth: [] components: + parameters: + PaginationPrefix: + in: query + name: prefix + description: return items prefixed with this value + schema: + type: string + + PaginationAfter: + in: query + name: after + description: return items after this value + schema: + type: string + + PaginationAmount: + in: query + name: amount + description: how many items to return + schema: + type: integer + minimum: -1 + maximum: 1000 + default: 100 + + PaginationDelimiter: + in: query + name: delimiter + description: delimiter used to group common prefixes by + schema: + type: string + securitySchemes: basic_auth: type: http @@ -488,6 +520,7 @@ paths: - common operationId: getVersion summary: return program and runtime version + security: [] # No authentication responses: 200: description: program version @@ -1344,6 +1377,7 @@ paths: - auth operationId: register summary: perform user registration + security: [] # No authentication requestBody: content: application/json: From 176a1366596b0a95bd097209a3ef69de6f9e165a Mon Sep 17 00:00:00 2001 From: zjy Date: Mon, 18 Dec 2023 14:28:14 +0800 Subject: [PATCH 084/210] fix: fix authentication mechanism --- api/api_impl/server.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/api_impl/server.go b/api/api_impl/server.go index 10ab5b58..0afbad16 100644 --- a/api/api_impl/server.go +++ b/api/api_impl/server.go @@ -3,10 +3,11 @@ package apiimpl import ( "context" "errors" - "github.com/jiaozifs/jiaozifs/auth" "net" "net/http" + "github.com/jiaozifs/jiaozifs/auth" + "github.com/jiaozifs/jiaozifs/auth/crypt" "github.com/MadAppGang/httplog" From 96f44f1b511bbc4e7dbe55aa736953956284e161 Mon Sep 17 00:00:00 2001 From: brown Date: Mon, 18 Dec 2023 14:39:39 +0800 Subject: [PATCH 085/210] fix: generate api --- api/resp.gen.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/resp.gen.go b/api/resp.gen.go index 0cfccc98..b726ce38 100644 --- a/api/resp.gen.go +++ b/api/resp.gen.go @@ -3,7 +3,7 @@ // // Generated by this command: // -// mockgen.exe --package=api --destination=resp.gen.go net/http ResponseWriter +// mockgen --package=api --destination=resp.gen.go net/http ResponseWriter // // Package api is a generated GoMock package. package api From 890440efa6691f26438b50a742a17e1dc08fc8c5 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Mon, 18 Dec 2023 16:47:12 +0800 Subject: [PATCH 086/210] test: basic integration test --- api/custom_response.go | 3 + api/jiaozifs.gen.go | 2723 +++++++++++++----------- api/swagger.yml | 202 +- auth/auth_middleware.go | 4 +- auth/context.go | 4 +- cmd/init.go | 29 +- cmd/root.go | 8 +- config/config.go | 19 +- controller/branch_ctl.go | 141 +- controller/commit_ctl.go | 42 +- controller/object_ctl.go | 117 +- controller/repository_ctl.go | 182 +- controller/user_ctl.go | 4 +- controller/wip_ctl.go | 109 +- go.mod | 6 +- go.sum | 11 +- integrationtest/helper.go | 87 + integrationtest/repo_test.go | 97 + integrationtest/root_test.go | 17 + integrationtest/simple_running_test.go | 13 + integrationtest/user_test.go | 55 + models/merge_request.go | 2 +- models/ref.go | 47 +- models/ref_test.go | 11 + models/repository.go | 31 +- models/wip.go | 4 +- 26 files changed, 2465 insertions(+), 1503 deletions(-) create mode 100644 integrationtest/helper.go create mode 100644 integrationtest/repo_test.go create mode 100644 integrationtest/root_test.go create mode 100644 integrationtest/simple_running_test.go create mode 100644 integrationtest/user_test.go diff --git a/api/custom_response.go b/api/custom_response.go index 4eb5986b..70a9a11e 100644 --- a/api/custom_response.go +++ b/api/custom_response.go @@ -40,6 +40,9 @@ func (response *JiaozifsResponse) OK() { func (response *JiaozifsResponse) NotFound() { response.WriteHeader(http.StatusNotFound) } +func (response *JiaozifsResponse) Forbidden() { + response.WriteHeader(http.StatusForbidden) +} // Unauthorized response with 401 func (response *JiaozifsResponse) Unauthorized() { diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 956a2b3c..c29e94c3 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -148,8 +148,14 @@ type Pagination struct { // Ref defines model for Ref. type Ref struct { - CommitHash string `json:"CommitHash"` - Name string `json:"Name"` + CommitHash string `json:"CommitHash"` + CreatedAt time.Time `json:"CreatedAt"` + CreatorID openapi_types.UUID `json:"CreatorID"` + Description *string `json:"Description,omitempty"` + ID openapi_types.UUID `json:"ID"` + Name string `json:"Name"` + RepositoryID openapi_types.UUID `json:"RepositoryID"` + UpdatedAt time.Time `json:"UpdatedAt"` } // RefList defines model for RefList. @@ -305,6 +311,16 @@ type UploadObjectParams struct { IfNoneMatch *string `json:"If-None-Match,omitempty"` } +// DeleteBranchParams defines parameters for DeleteBranch. +type DeleteBranchParams struct { + RefName string `form:"refName" json:"refName"` +} + +// GetBranchParams defines parameters for GetBranch. +type GetBranchParams struct { + RefName string `form:"refName" json:"refName"` +} + // GetCommitsInRepositoryParams defines parameters for GetCommitsInRepository. type GetCommitsInRepositoryParams struct { // RefName ref(branch/tag) name @@ -317,12 +333,12 @@ type GetCommitDiffParams struct { Path *string `form:"path,omitempty" json:"path,omitempty"` } -// GetEntriesInCommitParams defines parameters for GetEntriesInCommit. -type GetEntriesInCommitParams struct { +// GetEntriesInRefParams defines parameters for GetEntriesInRef. +type GetEntriesInRefParams struct { // Path specific path, if not specific return entries in root Path *string `form:"path,omitempty" json:"path,omitempty"` - // Ref specific ref + // Ref specific ref default to main branch Ref *string `form:"ref,omitempty" json:"ref,omitempty"` } @@ -374,15 +390,15 @@ type UploadObjectMultipartRequestBody UploadObjectMultipartBody // UpdateRepositoryJSONRequestBody defines body for UpdateRepository for application/json ContentType. type UpdateRepositoryJSONRequestBody = UpdateRepository +// CreateBranchJSONRequestBody defines body for CreateBranch for application/json ContentType. +type CreateBranchJSONRequestBody = BranchCreation + // RegisterJSONRequestBody defines body for Register for application/json ContentType. type RegisterJSONRequestBody = UserRegisterInfo // CreateRepositoryJSONRequestBody defines body for CreateRepository for application/json ContentType. type CreateRepositoryJSONRequestBody = CreateRepository -// CreateBranchJSONRequestBody defines body for CreateBranch for application/json ContentType. -type CreateBranchJSONRequestBody = BranchCreation - // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error @@ -465,36 +481,50 @@ type ClientInterface interface { Logout(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) // DeleteObject request - DeleteObject(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) + DeleteObject(ctx context.Context, owner string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetObject request - GetObject(ctx context.Context, user string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) + GetObject(ctx context.Context, owner string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) // HeadObject request - HeadObject(ctx context.Context, user string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) + HeadObject(ctx context.Context, owner string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) // UploadObjectWithBody request with any body - UploadObjectWithBody(ctx context.Context, user string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + UploadObjectWithBody(ctx context.Context, owner string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) // DeleteRepository request - DeleteRepository(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) + DeleteRepository(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) // GetRepository request - GetRepository(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) + GetRepository(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) // UpdateRepositoryWithBody request with any body - UpdateRepositoryWithBody(ctx context.Context, user string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + UpdateRepositoryWithBody(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + UpdateRepository(ctx context.Context, owner string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // DeleteBranch request + DeleteBranch(ctx context.Context, owner string, repository string, params *DeleteBranchParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetBranch request + GetBranch(ctx context.Context, owner string, repository string, params *GetBranchParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateBranchWithBody request with any body + CreateBranchWithBody(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateBranch(ctx context.Context, owner string, repository string, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - UpdateRepository(ctx context.Context, user string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // ListBranches request + ListBranches(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) // GetCommitsInRepository request - GetCommitsInRepository(ctx context.Context, user string, repository string, params *GetCommitsInRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) + GetCommitsInRepository(ctx context.Context, owner string, repository string, params *GetCommitsInRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetCommitDiff request - GetCommitDiff(ctx context.Context, user string, repository string, basehead string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*http.Response, error) + GetCommitDiff(ctx context.Context, owner string, repository string, basehead string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*http.Response, error) - // GetEntriesInCommit request - GetEntriesInCommit(ctx context.Context, user string, repository string, params *GetEntriesInCommitParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetEntriesInRef request + GetEntriesInRef(ctx context.Context, owner string, repository string, params *GetEntriesInRefParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetSetupState request GetSetupState(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -504,8 +534,8 @@ type ClientInterface interface { Register(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // ListRepository request - ListRepository(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // ListRepositoryOfAuthenticatedUser request + ListRepositoryOfAuthenticatedUser(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) // CreateRepositoryWithBody request with any body CreateRepositoryWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -515,40 +545,29 @@ type ClientInterface interface { // GetUserInfo request GetUserInfo(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // ListRepository request + ListRepository(ctx context.Context, owner string, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetVersion request GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) // DeleteWip request - DeleteWip(ctx context.Context, repository string, params *DeleteWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) + DeleteWip(ctx context.Context, owner string, repository string, params *DeleteWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetWip request - GetWip(ctx context.Context, repository string, params *GetWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) + GetWip(ctx context.Context, owner string, repository string, params *GetWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) // CreateWip request - CreateWip(ctx context.Context, repository string, params *CreateWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) + CreateWip(ctx context.Context, owner string, repository string, params *CreateWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetWipChanges request - GetWipChanges(ctx context.Context, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*http.Response, error) + GetWipChanges(ctx context.Context, owner string, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*http.Response, error) // CommitWip request - CommitWip(ctx context.Context, repository string, params *CommitWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) + CommitWip(ctx context.Context, owner string, repository string, params *CommitWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) // ListWip request - ListWip(ctx context.Context, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) - - // ListBranches request - ListBranches(ctx context.Context, user string, reopsitory string, reqEditors ...RequestEditorFn) (*http.Response, error) - - // CreateBranchWithBody request with any body - CreateBranchWithBody(ctx context.Context, user string, reopsitory string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - CreateBranch(ctx context.Context, user string, reopsitory string, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // DeleteBranch request - DeleteBranch(ctx context.Context, user string, repository string, branch string, reqEditors ...RequestEditorFn) (*http.Response, error) - - // GetBranch request - GetBranch(ctx context.Context, user string, repository string, branch string, reqEditors ...RequestEditorFn) (*http.Response, error) + ListWip(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) } func (c *Client) LoginWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -587,8 +606,8 @@ func (c *Client) Logout(ctx context.Context, reqEditors ...RequestEditorFn) (*ht return c.Client.Do(req) } -func (c *Client) DeleteObject(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteObjectRequest(c.Server, user, repository, params) +func (c *Client) DeleteObject(ctx context.Context, owner string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteObjectRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -599,8 +618,8 @@ func (c *Client) DeleteObject(ctx context.Context, user string, repository strin return c.Client.Do(req) } -func (c *Client) GetObject(ctx context.Context, user string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetObjectRequest(c.Server, user, repository, params) +func (c *Client) GetObject(ctx context.Context, owner string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetObjectRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -611,8 +630,8 @@ func (c *Client) GetObject(ctx context.Context, user string, repository string, return c.Client.Do(req) } -func (c *Client) HeadObject(ctx context.Context, user string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewHeadObjectRequest(c.Server, user, repository, params) +func (c *Client) HeadObject(ctx context.Context, owner string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewHeadObjectRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -623,8 +642,8 @@ func (c *Client) HeadObject(ctx context.Context, user string, repository string, return c.Client.Do(req) } -func (c *Client) UploadObjectWithBody(ctx context.Context, user string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewUploadObjectRequestWithBody(c.Server, user, repository, params, contentType, body) +func (c *Client) UploadObjectWithBody(ctx context.Context, owner string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUploadObjectRequestWithBody(c.Server, owner, repository, params, contentType, body) if err != nil { return nil, err } @@ -635,8 +654,8 @@ func (c *Client) UploadObjectWithBody(ctx context.Context, user string, reposito return c.Client.Do(req) } -func (c *Client) DeleteRepository(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteRepositoryRequest(c.Server, user, repository) +func (c *Client) DeleteRepository(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteRepositoryRequest(c.Server, owner, repository) if err != nil { return nil, err } @@ -647,8 +666,8 @@ func (c *Client) DeleteRepository(ctx context.Context, user string, repository s return c.Client.Do(req) } -func (c *Client) GetRepository(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetRepositoryRequest(c.Server, user, repository) +func (c *Client) GetRepository(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetRepositoryRequest(c.Server, owner, repository) if err != nil { return nil, err } @@ -659,8 +678,8 @@ func (c *Client) GetRepository(ctx context.Context, user string, repository stri return c.Client.Do(req) } -func (c *Client) UpdateRepositoryWithBody(ctx context.Context, user string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewUpdateRepositoryRequestWithBody(c.Server, user, repository, contentType, body) +func (c *Client) UpdateRepositoryWithBody(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateRepositoryRequestWithBody(c.Server, owner, repository, contentType, body) if err != nil { return nil, err } @@ -671,8 +690,8 @@ func (c *Client) UpdateRepositoryWithBody(ctx context.Context, user string, repo return c.Client.Do(req) } -func (c *Client) UpdateRepository(ctx context.Context, user string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewUpdateRepositoryRequest(c.Server, user, repository, body) +func (c *Client) UpdateRepository(ctx context.Context, owner string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateRepositoryRequest(c.Server, owner, repository, body) if err != nil { return nil, err } @@ -683,8 +702,8 @@ func (c *Client) UpdateRepository(ctx context.Context, user string, repository s return c.Client.Do(req) } -func (c *Client) GetCommitsInRepository(ctx context.Context, user string, repository string, params *GetCommitsInRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetCommitsInRepositoryRequest(c.Server, user, repository, params) +func (c *Client) DeleteBranch(ctx context.Context, owner string, repository string, params *DeleteBranchParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteBranchRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -695,8 +714,8 @@ func (c *Client) GetCommitsInRepository(ctx context.Context, user string, reposi return c.Client.Do(req) } -func (c *Client) GetCommitDiff(ctx context.Context, user string, repository string, basehead string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetCommitDiffRequest(c.Server, user, repository, basehead, params) +func (c *Client) GetBranch(ctx context.Context, owner string, repository string, params *GetBranchParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetBranchRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -707,8 +726,8 @@ func (c *Client) GetCommitDiff(ctx context.Context, user string, repository stri return c.Client.Do(req) } -func (c *Client) GetEntriesInCommit(ctx context.Context, user string, repository string, params *GetEntriesInCommitParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetEntriesInCommitRequest(c.Server, user, repository, params) +func (c *Client) CreateBranchWithBody(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateBranchRequestWithBody(c.Server, owner, repository, contentType, body) if err != nil { return nil, err } @@ -719,8 +738,8 @@ func (c *Client) GetEntriesInCommit(ctx context.Context, user string, repository return c.Client.Do(req) } -func (c *Client) GetSetupState(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetSetupStateRequest(c.Server) +func (c *Client) CreateBranch(ctx context.Context, owner string, repository string, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateBranchRequest(c.Server, owner, repository, body) if err != nil { return nil, err } @@ -731,8 +750,8 @@ func (c *Client) GetSetupState(ctx context.Context, reqEditors ...RequestEditorF return c.Client.Do(req) } -func (c *Client) RegisterWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewRegisterRequestWithBody(c.Server, contentType, body) +func (c *Client) ListBranches(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListBranchesRequest(c.Server, owner, repository) if err != nil { return nil, err } @@ -743,8 +762,8 @@ func (c *Client) RegisterWithBody(ctx context.Context, contentType string, body return c.Client.Do(req) } -func (c *Client) Register(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewRegisterRequest(c.Server, body) +func (c *Client) GetCommitsInRepository(ctx context.Context, owner string, repository string, params *GetCommitsInRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetCommitsInRepositoryRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -755,8 +774,8 @@ func (c *Client) Register(ctx context.Context, body RegisterJSONRequestBody, req return c.Client.Do(req) } -func (c *Client) ListRepository(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewListRepositoryRequest(c.Server) +func (c *Client) GetCommitDiff(ctx context.Context, owner string, repository string, basehead string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetCommitDiffRequest(c.Server, owner, repository, basehead, params) if err != nil { return nil, err } @@ -767,8 +786,8 @@ func (c *Client) ListRepository(ctx context.Context, reqEditors ...RequestEditor return c.Client.Do(req) } -func (c *Client) CreateRepositoryWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateRepositoryRequestWithBody(c.Server, contentType, body) +func (c *Client) GetEntriesInRef(ctx context.Context, owner string, repository string, params *GetEntriesInRefParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetEntriesInRefRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -779,8 +798,8 @@ func (c *Client) CreateRepositoryWithBody(ctx context.Context, contentType strin return c.Client.Do(req) } -func (c *Client) CreateRepository(ctx context.Context, body CreateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateRepositoryRequest(c.Server, body) +func (c *Client) GetSetupState(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetSetupStateRequest(c.Server) if err != nil { return nil, err } @@ -791,8 +810,8 @@ func (c *Client) CreateRepository(ctx context.Context, body CreateRepositoryJSON return c.Client.Do(req) } -func (c *Client) GetUserInfo(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetUserInfoRequest(c.Server) +func (c *Client) RegisterWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRegisterRequestWithBody(c.Server, contentType, body) if err != nil { return nil, err } @@ -803,8 +822,20 @@ func (c *Client) GetUserInfo(ctx context.Context, reqEditors ...RequestEditorFn) return c.Client.Do(req) } -func (c *Client) GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetVersionRequest(c.Server) +func (c *Client) Register(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRegisterRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) ListRepositoryOfAuthenticatedUser(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListRepositoryOfAuthenticatedUserRequest(c.Server) if err != nil { return nil, err } @@ -815,8 +846,8 @@ func (c *Client) GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) return c.Client.Do(req) } -func (c *Client) DeleteWip(ctx context.Context, repository string, params *DeleteWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteWipRequest(c.Server, repository, params) +func (c *Client) CreateRepositoryWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateRepositoryRequestWithBody(c.Server, contentType, body) if err != nil { return nil, err } @@ -827,8 +858,8 @@ func (c *Client) DeleteWip(ctx context.Context, repository string, params *Delet return c.Client.Do(req) } -func (c *Client) GetWip(ctx context.Context, repository string, params *GetWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetWipRequest(c.Server, repository, params) +func (c *Client) CreateRepository(ctx context.Context, body CreateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateRepositoryRequest(c.Server, body) if err != nil { return nil, err } @@ -839,8 +870,8 @@ func (c *Client) GetWip(ctx context.Context, repository string, params *GetWipPa return c.Client.Do(req) } -func (c *Client) CreateWip(ctx context.Context, repository string, params *CreateWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateWipRequest(c.Server, repository, params) +func (c *Client) GetUserInfo(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetUserInfoRequest(c.Server) if err != nil { return nil, err } @@ -851,8 +882,8 @@ func (c *Client) CreateWip(ctx context.Context, repository string, params *Creat return c.Client.Do(req) } -func (c *Client) GetWipChanges(ctx context.Context, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetWipChangesRequest(c.Server, repository, params) +func (c *Client) ListRepository(ctx context.Context, owner string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListRepositoryRequest(c.Server, owner) if err != nil { return nil, err } @@ -863,8 +894,8 @@ func (c *Client) GetWipChanges(ctx context.Context, repository string, params *G return c.Client.Do(req) } -func (c *Client) CommitWip(ctx context.Context, repository string, params *CommitWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCommitWipRequest(c.Server, repository, params) +func (c *Client) GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetVersionRequest(c.Server) if err != nil { return nil, err } @@ -875,8 +906,8 @@ func (c *Client) CommitWip(ctx context.Context, repository string, params *Commi return c.Client.Do(req) } -func (c *Client) ListWip(ctx context.Context, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewListWipRequest(c.Server, repository) +func (c *Client) DeleteWip(ctx context.Context, owner string, repository string, params *DeleteWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteWipRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -887,8 +918,8 @@ func (c *Client) ListWip(ctx context.Context, repository string, reqEditors ...R return c.Client.Do(req) } -func (c *Client) ListBranches(ctx context.Context, user string, reopsitory string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewListBranchesRequest(c.Server, user, reopsitory) +func (c *Client) GetWip(ctx context.Context, owner string, repository string, params *GetWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetWipRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -899,8 +930,8 @@ func (c *Client) ListBranches(ctx context.Context, user string, reopsitory strin return c.Client.Do(req) } -func (c *Client) CreateBranchWithBody(ctx context.Context, user string, reopsitory string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateBranchRequestWithBody(c.Server, user, reopsitory, contentType, body) +func (c *Client) CreateWip(ctx context.Context, owner string, repository string, params *CreateWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateWipRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -911,8 +942,8 @@ func (c *Client) CreateBranchWithBody(ctx context.Context, user string, reopsito return c.Client.Do(req) } -func (c *Client) CreateBranch(ctx context.Context, user string, reopsitory string, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateBranchRequest(c.Server, user, reopsitory, body) +func (c *Client) GetWipChanges(ctx context.Context, owner string, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetWipChangesRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -923,8 +954,8 @@ func (c *Client) CreateBranch(ctx context.Context, user string, reopsitory strin return c.Client.Do(req) } -func (c *Client) DeleteBranch(ctx context.Context, user string, repository string, branch string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteBranchRequest(c.Server, user, repository, branch) +func (c *Client) CommitWip(ctx context.Context, owner string, repository string, params *CommitWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCommitWipRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -935,8 +966,8 @@ func (c *Client) DeleteBranch(ctx context.Context, user string, repository strin return c.Client.Do(req) } -func (c *Client) GetBranch(ctx context.Context, user string, repository string, branch string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetBranchRequest(c.Server, user, repository, branch) +func (c *Client) ListWip(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListWipRequest(c.Server, owner, repository) if err != nil { return nil, err } @@ -1015,12 +1046,12 @@ func NewLogoutRequest(server string) (*http.Request, error) { } // NewDeleteObjectRequest generates requests for DeleteObject -func NewDeleteObjectRequest(server string, user string, repository string, params *DeleteObjectParams) (*http.Request, error) { +func NewDeleteObjectRequest(server string, owner string, repository string, params *DeleteObjectParams) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } @@ -1098,12 +1129,12 @@ func NewDeleteObjectRequest(server string, user string, repository string, param } // NewGetObjectRequest generates requests for GetObject -func NewGetObjectRequest(server string, user string, repository string, params *GetObjectParams) (*http.Request, error) { +func NewGetObjectRequest(server string, owner string, repository string, params *GetObjectParams) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } @@ -1184,12 +1215,12 @@ func NewGetObjectRequest(server string, user string, repository string, params * } // NewHeadObjectRequest generates requests for HeadObject -func NewHeadObjectRequest(server string, user string, repository string, params *HeadObjectParams) (*http.Request, error) { +func NewHeadObjectRequest(server string, owner string, repository string, params *HeadObjectParams) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } @@ -1270,12 +1301,12 @@ func NewHeadObjectRequest(server string, user string, repository string, params } // NewUploadObjectRequestWithBody generates requests for UploadObject with any type of body -func NewUploadObjectRequestWithBody(server string, user string, repository string, params *UploadObjectParams, contentType string, body io.Reader) (*http.Request, error) { +func NewUploadObjectRequestWithBody(server string, owner string, repository string, params *UploadObjectParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } @@ -1370,12 +1401,12 @@ func NewUploadObjectRequestWithBody(server string, user string, repository strin } // NewDeleteRepositoryRequest generates requests for DeleteRepository -func NewDeleteRepositoryRequest(server string, user string, repository string) (*http.Request, error) { +func NewDeleteRepositoryRequest(server string, owner string, repository string) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } @@ -1411,12 +1442,12 @@ func NewDeleteRepositoryRequest(server string, user string, repository string) ( } // NewGetRepositoryRequest generates requests for GetRepository -func NewGetRepositoryRequest(server string, user string, repository string) (*http.Request, error) { +func NewGetRepositoryRequest(server string, owner string, repository string) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } @@ -1452,23 +1483,23 @@ func NewGetRepositoryRequest(server string, user string, repository string) (*ht } // NewUpdateRepositoryRequest calls the generic UpdateRepository builder with application/json body -func NewUpdateRepositoryRequest(server string, user string, repository string, body UpdateRepositoryJSONRequestBody) (*http.Request, error) { +func NewUpdateRepositoryRequest(server string, owner string, repository string, body UpdateRepositoryJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewUpdateRepositoryRequestWithBody(server, user, repository, "application/json", bodyReader) + return NewUpdateRepositoryRequestWithBody(server, owner, repository, "application/json", bodyReader) } // NewUpdateRepositoryRequestWithBody generates requests for UpdateRepository with any type of body -func NewUpdateRepositoryRequestWithBody(server string, user string, repository string, contentType string, body io.Reader) (*http.Request, error) { +func NewUpdateRepositoryRequestWithBody(server string, owner string, repository string, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } @@ -1505,13 +1536,13 @@ func NewUpdateRepositoryRequestWithBody(server string, user string, repository s return req, nil } -// NewGetCommitsInRepositoryRequest generates requests for GetCommitsInRepository -func NewGetCommitsInRepositoryRequest(server string, user string, repository string, params *GetCommitsInRepositoryParams) (*http.Request, error) { +// NewDeleteBranchRequest generates requests for DeleteBranch +func NewDeleteBranchRequest(server string, owner string, repository string, params *DeleteBranchParams) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } @@ -1528,7 +1559,7 @@ func NewGetCommitsInRepositoryRequest(server string, user string, repository str return nil, err } - operationPath := fmt.Sprintf("/repos/%s/%s/commits", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/repos/%s/%s/branch", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1541,26 +1572,22 @@ func NewGetCommitsInRepositoryRequest(server string, user string, repository str if params != nil { queryValues := queryURL.Query() - if params.RefName != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, *params.RefName); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) } } - } queryURL.RawQuery = queryValues.Encode() } - req, err := http.NewRequest("GET", queryURL.String(), nil) + req, err := http.NewRequest("DELETE", queryURL.String(), nil) if err != nil { return nil, err } @@ -1568,13 +1595,13 @@ func NewGetCommitsInRepositoryRequest(server string, user string, repository str return req, nil } -// NewGetCommitDiffRequest generates requests for GetCommitDiff -func NewGetCommitDiffRequest(server string, user string, repository string, basehead string, params *GetCommitDiffParams) (*http.Request, error) { +// NewGetBranchRequest generates requests for GetBranch +func NewGetBranchRequest(server string, owner string, repository string, params *GetBranchParams) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } @@ -1586,19 +1613,12 @@ func NewGetCommitDiffRequest(server string, user string, repository string, base return nil, err } - var pathParam2 string - - pathParam2, err = runtime.StyleParamWithLocation("simple", false, "basehead", runtime.ParamLocationPath, basehead) - if err != nil { - return nil, err - } - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/repos/%s/%s/compare/%s", pathParam0, pathParam1, pathParam2) + operationPath := fmt.Sprintf("/repos/%s/%s/branch", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1611,20 +1631,16 @@ func NewGetCommitDiffRequest(server string, user string, repository string, base if params != nil { queryValues := queryURL.Query() - if params.Path != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) } } - } queryURL.RawQuery = queryValues.Encode() @@ -1638,13 +1654,24 @@ func NewGetCommitDiffRequest(server string, user string, repository string, base return req, nil } -// NewGetEntriesInCommitRequest generates requests for GetEntriesInCommit -func NewGetEntriesInCommitRequest(server string, user string, repository string, params *GetEntriesInCommitParams) (*http.Request, error) { +// NewCreateBranchRequest calls the generic CreateBranch builder with application/json body +func NewCreateBranchRequest(server string, owner string, repository string, body CreateBranchJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateBranchRequestWithBody(server, owner, repository, "application/json", bodyReader) +} + +// NewCreateBranchRequestWithBody generates requests for CreateBranch with any type of body +func NewCreateBranchRequestWithBody(server string, owner string, repository string, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } @@ -1661,7 +1688,7 @@ func NewGetEntriesInCommitRequest(server string, user string, repository string, return nil, err } - operationPath := fmt.Sprintf("/repos/%s/%s/contents/", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/repos/%s/%s/branch", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1671,62 +1698,40 @@ func NewGetEntriesInCommitRequest(server string, user string, repository string, return nil, err } - if params != nil { - queryValues := queryURL.Query() - - if params.Path != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } - if params.Ref != nil { + req.Header.Add("Content-Type", contentType) - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "ref", runtime.ParamLocationQuery, *params.Ref); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } + return req, nil +} - } +// NewListBranchesRequest generates requests for ListBranches +func NewListBranchesRequest(server string, owner string, repository string) (*http.Request, error) { + var err error - queryURL.RawQuery = queryValues.Encode() - } + var pathParam0 string - req, err := http.NewRequest("GET", queryURL.String(), nil) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } - return req, nil -} + var pathParam1 string -// NewGetSetupStateRequest generates requests for GetSetupState -func NewGetSetupStateRequest(server string) (*http.Request, error) { - var err error + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/setup") + operationPath := fmt.Sprintf("/repos/%s/%s/branches", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1744,27 +1749,30 @@ func NewGetSetupStateRequest(server string) (*http.Request, error) { return req, nil } -// NewRegisterRequest calls the generic Register builder with application/json body -func NewRegisterRequest(server string, body RegisterJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) +// NewGetCommitsInRepositoryRequest generates requests for GetCommitsInRepository +func NewGetCommitsInRepositoryRequest(server string, owner string, repository string, params *GetCommitsInRepositoryParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } - bodyReader = bytes.NewReader(buf) - return NewRegisterRequestWithBody(server, "application/json", bodyReader) -} -// NewRegisterRequestWithBody generates requests for Register with any type of body -func NewRegisterRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/users/register") + operationPath := fmt.Sprintf("/repos/%s/%s/commits", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1774,64 +1782,67 @@ func NewRegisterRequestWithBody(server string, contentType string, body io.Reade return nil, err } - req, err := http.NewRequest("POST", queryURL.String(), body) + if params != nil { + queryValues := queryURL.Query() + + if params.RefName != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, *params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } - req.Header.Add("Content-Type", contentType) - return req, nil } -// NewListRepositoryRequest generates requests for ListRepository -func NewListRepositoryRequest(server string) (*http.Request, error) { +// NewGetCommitDiffRequest generates requests for GetCommitDiff +func NewGetCommitDiffRequest(server string, owner string, repository string, basehead string, params *GetCommitDiffParams) (*http.Request, error) { var err error - serverURL, err := url.Parse(server) + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/users/repos") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } + var pathParam1 string - queryURL, err := serverURL.Parse(operationPath) + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) if err != nil { return nil, err } - req, err := http.NewRequest("GET", queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} + var pathParam2 string -// NewCreateRepositoryRequest calls the generic CreateRepository builder with application/json body -func NewCreateRepositoryRequest(server string, body CreateRepositoryJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "basehead", runtime.ParamLocationPath, basehead) if err != nil { return nil, err } - bodyReader = bytes.NewReader(buf) - return NewCreateRepositoryRequestWithBody(server, "application/json", bodyReader) -} - -// NewCreateRepositoryRequestWithBody generates requests for CreateRepository with any type of body -func NewCreateRepositoryRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/users/repos") + operationPath := fmt.Sprintf("/repos/%s/%s/compare/%s", pathParam0, pathParam1, pathParam2) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1841,26 +1852,60 @@ func NewCreateRepositoryRequestWithBody(server string, contentType string, body return nil, err } - req, err := http.NewRequest("POST", queryURL.String(), body) + if params != nil { + queryValues := queryURL.Query() + + if params.Path != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } - req.Header.Add("Content-Type", contentType) - return req, nil } -// NewGetUserInfoRequest generates requests for GetUserInfo -func NewGetUserInfoRequest(server string) (*http.Request, error) { +// NewGetEntriesInRefRequest generates requests for GetEntriesInRef +func NewGetEntriesInRefRequest(server string, owner string, repository string, params *GetEntriesInRefParams) (*http.Request, error) { var err error + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/users/user") + operationPath := fmt.Sprintf("/repos/%s/%s/contents/", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1870,6 +1915,44 @@ func NewGetUserInfoRequest(server string) (*http.Request, error) { return nil, err } + if params != nil { + queryValues := queryURL.Query() + + if params.Path != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Ref != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "ref", runtime.ParamLocationQuery, *params.Ref); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err @@ -1878,8 +1961,8 @@ func NewGetUserInfoRequest(server string) (*http.Request, error) { return req, nil } -// NewGetVersionRequest generates requests for GetVersion -func NewGetVersionRequest(server string) (*http.Request, error) { +// NewGetSetupStateRequest generates requests for GetSetupState +func NewGetSetupStateRequest(server string) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -1887,7 +1970,7 @@ func NewGetVersionRequest(server string) (*http.Request, error) { return nil, err } - operationPath := fmt.Sprintf("/version") + operationPath := fmt.Sprintf("/setup") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1905,23 +1988,27 @@ func NewGetVersionRequest(server string) (*http.Request, error) { return req, nil } -// NewDeleteWipRequest generates requests for DeleteWip -func NewDeleteWipRequest(server string, repository string, params *DeleteWipParams) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) +// NewRegisterRequest calls the generic Register builder with application/json body +func NewRegisterRequest(server string, body RegisterJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) if err != nil { return nil, err } + bodyReader = bytes.NewReader(buf) + return NewRegisterRequestWithBody(server, "application/json", bodyReader) +} + +// NewRegisterRequestWithBody generates requests for Register with any type of body +func NewRegisterRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/wip/%s", pathParam0) + operationPath := fmt.Sprintf("/users/register") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1931,49 +2018,26 @@ func NewDeleteWipRequest(server string, repository string, params *DeleteWipPara return nil, err } - if params != nil { - queryValues := queryURL.Query() - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - queryURL.RawQuery = queryValues.Encode() - } - - req, err := http.NewRequest("DELETE", queryURL.String(), nil) + req, err := http.NewRequest("POST", queryURL.String(), body) if err != nil { return nil, err } + req.Header.Add("Content-Type", contentType) + return req, nil } -// NewGetWipRequest generates requests for GetWip -func NewGetWipRequest(server string, repository string, params *GetWipParams) (*http.Request, error) { +// NewListRepositoryOfAuthenticatedUserRequest generates requests for ListRepositoryOfAuthenticatedUser +func NewListRepositoryOfAuthenticatedUserRequest(server string) (*http.Request, error) { var err error - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) - if err != nil { - return nil, err - } - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/wip/%s", pathParam0) + operationPath := fmt.Sprintf("/users/repos") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1983,24 +2047,6 @@ func NewGetWipRequest(server string, repository string, params *GetWipParams) (* return nil, err } - if params != nil { - queryValues := queryURL.Query() - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - queryURL.RawQuery = queryValues.Encode() - } - req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err @@ -2009,23 +2055,27 @@ func NewGetWipRequest(server string, repository string, params *GetWipParams) (* return req, nil } -// NewCreateWipRequest generates requests for CreateWip -func NewCreateWipRequest(server string, repository string, params *CreateWipParams) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) +// NewCreateRepositoryRequest calls the generic CreateRepository builder with application/json body +func NewCreateRepositoryRequest(server string, body CreateRepositoryJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) if err != nil { return nil, err } + bodyReader = bytes.NewReader(buf) + return NewCreateRepositoryRequestWithBody(server, "application/json", bodyReader) +} + +// NewCreateRepositoryRequestWithBody generates requests for CreateRepository with any type of body +func NewCreateRepositoryRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/wip/%s", pathParam0) + operationPath := fmt.Sprintf("/users/repos") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2035,37 +2085,36 @@ func NewCreateWipRequest(server string, repository string, params *CreateWipPara return nil, err } - if params != nil { - queryValues := queryURL.Query() + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "name", runtime.ParamLocationQuery, params.Name); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } + req.Header.Add("Content-Type", contentType) - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } + return req, nil +} - queryURL.RawQuery = queryValues.Encode() +// NewGetUserInfoRequest generates requests for GetUserInfo +func NewGetUserInfoRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err } - req, err := http.NewRequest("POST", queryURL.String(), nil) + operationPath := fmt.Sprintf("/users/user") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } @@ -2073,13 +2122,13 @@ func NewCreateWipRequest(server string, repository string, params *CreateWipPara return req, nil } -// NewGetWipChangesRequest generates requests for GetWipChanges -func NewGetWipChangesRequest(server string, repository string, params *GetWipChangesParams) (*http.Request, error) { +// NewListRepositoryRequest generates requests for ListRepository +func NewListRepositoryRequest(server string, owner string) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } @@ -2089,7 +2138,7 @@ func NewGetWipChangesRequest(server string, repository string, params *GetWipCha return nil, err } - operationPath := fmt.Sprintf("/wip/%s/changes", pathParam0) + operationPath := fmt.Sprintf("/users/%s/repos", pathParam0) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2099,38 +2148,31 @@ func NewGetWipChangesRequest(server string, repository string, params *GetWipCha return nil, err } - if params != nil { - queryValues := queryURL.Query() + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } + return req, nil +} - if params.Path != nil { +// NewGetVersionRequest generates requests for GetVersion +func NewGetVersionRequest(server string) (*http.Request, error) { + var err error - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } - } + operationPath := fmt.Sprintf("/version") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } - queryURL.RawQuery = queryValues.Encode() + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err } req, err := http.NewRequest("GET", queryURL.String(), nil) @@ -2141,13 +2183,20 @@ func NewGetWipChangesRequest(server string, repository string, params *GetWipCha return req, nil } -// NewCommitWipRequest generates requests for CommitWip -func NewCommitWipRequest(server string, repository string, params *CommitWipParams) (*http.Request, error) { +// NewDeleteWipRequest generates requests for DeleteWip +func NewDeleteWipRequest(server string, owner string, repository string, params *DeleteWipParams) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) if err != nil { return nil, err } @@ -2157,7 +2206,7 @@ func NewCommitWipRequest(server string, repository string, params *CommitWipPara return nil, err } - operationPath := fmt.Sprintf("/wip/%s/commit", pathParam0) + operationPath := fmt.Sprintf("/wip/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2170,22 +2219,6 @@ func NewCommitWipRequest(server string, repository string, params *CommitWipPara if params != nil { queryValues := queryURL.Query() - if params.Msg != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "msg", runtime.ParamLocationQuery, *params.Msg); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { @@ -2201,7 +2234,7 @@ func NewCommitWipRequest(server string, repository string, params *CommitWipPara queryURL.RawQuery = queryValues.Encode() } - req, err := http.NewRequest("POST", queryURL.String(), nil) + req, err := http.NewRequest("DELETE", queryURL.String(), nil) if err != nil { return nil, err } @@ -2209,13 +2242,20 @@ func NewCommitWipRequest(server string, repository string, params *CommitWipPara return req, nil } -// NewListWipRequest generates requests for ListWip -func NewListWipRequest(server string, repository string) (*http.Request, error) { +// NewGetWipRequest generates requests for GetWip +func NewGetWipRequest(server string, owner string, repository string, params *GetWipParams) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) if err != nil { return nil, err } @@ -2225,7 +2265,7 @@ func NewListWipRequest(server string, repository string) (*http.Request, error) return nil, err } - operationPath := fmt.Sprintf("/wip/%s/list", pathParam0) + operationPath := fmt.Sprintf("/wip/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2235,6 +2275,24 @@ func NewListWipRequest(server string, repository string) (*http.Request, error) return nil, err } + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err @@ -2243,20 +2301,20 @@ func NewListWipRequest(server string, repository string) (*http.Request, error) return req, nil } -// NewListBranchesRequest generates requests for ListBranches -func NewListBranchesRequest(server string, user string, reopsitory string) (*http.Request, error) { +// NewCreateWipRequest generates requests for CreateWip +func NewCreateWipRequest(server string, owner string, repository string, params *CreateWipParams) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } var pathParam1 string - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "reopsitory", runtime.ParamLocationPath, reopsitory) + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) if err != nil { return nil, err } @@ -2266,7 +2324,7 @@ func NewListBranchesRequest(server string, user string, reopsitory string) (*htt return nil, err } - operationPath := fmt.Sprintf("/%s/%s/branches", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/wip/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2276,39 +2334,58 @@ func NewListBranchesRequest(server string, user string, reopsitory string) (*htt return nil, err } - req, err := http.NewRequest("GET", queryURL.String(), nil) - if err != nil { - return nil, err - } + if params != nil { + queryValues := queryURL.Query() - return req, nil -} + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "name", runtime.ParamLocationQuery, params.Name); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } -// NewCreateBranchRequest calls the generic CreateBranch builder with application/json body -func NewCreateBranchRequest(server string, user string, reopsitory string, body CreateBranchJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("POST", queryURL.String(), nil) if err != nil { return nil, err } - bodyReader = bytes.NewReader(buf) - return NewCreateBranchRequestWithBody(server, user, reopsitory, "application/json", bodyReader) + + return req, nil } -// NewCreateBranchRequestWithBody generates requests for CreateBranch with any type of body -func NewCreateBranchRequestWithBody(server string, user string, reopsitory string, contentType string, body io.Reader) (*http.Request, error) { +// NewGetWipChangesRequest generates requests for GetWipChanges +func NewGetWipChangesRequest(server string, owner string, repository string, params *GetWipChangesParams) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } var pathParam1 string - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "reopsitory", runtime.ParamLocationPath, reopsitory) + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) if err != nil { return nil, err } @@ -2318,7 +2395,7 @@ func NewCreateBranchRequestWithBody(server string, user string, reopsitory strin return nil, err } - operationPath := fmt.Sprintf("/%s/%s/branches", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/wip/%s/%s/changes", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2328,37 +2405,62 @@ func NewCreateBranchRequestWithBody(server string, user string, reopsitory strin return nil, err } - req, err := http.NewRequest("POST", queryURL.String(), body) + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + if params.Path != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } - req.Header.Add("Content-Type", contentType) - return req, nil } -// NewDeleteBranchRequest generates requests for DeleteBranch -func NewDeleteBranchRequest(server string, user string, repository string, branch string) (*http.Request, error) { +// NewCommitWipRequest generates requests for CommitWip +func NewCommitWipRequest(server string, owner string, repository string, params *CommitWipParams) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } var pathParam1 string - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) - if err != nil { - return nil, err - } - - var pathParam2 string - - pathParam2, err = runtime.StyleParamWithLocation("simple", false, "branch", runtime.ParamLocationPath, branch) + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) if err != nil { return nil, err } @@ -2368,7 +2470,7 @@ func NewDeleteBranchRequest(server string, user string, repository string, branc return nil, err } - operationPath := fmt.Sprintf("/%s/%s/branches/%s", pathParam0, pathParam1, pathParam2) + operationPath := fmt.Sprintf("/wip/%s/%s/commit", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2378,7 +2480,41 @@ func NewDeleteBranchRequest(server string, user string, repository string, branc return nil, err } - req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if params != nil { + queryValues := queryURL.Query() + + if params.Msg != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "msg", runtime.ParamLocationQuery, *params.Msg); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("POST", queryURL.String(), nil) if err != nil { return nil, err } @@ -2386,13 +2522,13 @@ func NewDeleteBranchRequest(server string, user string, repository string, branc return req, nil } -// NewGetBranchRequest generates requests for GetBranch -func NewGetBranchRequest(server string, user string, repository string, branch string) (*http.Request, error) { +// NewListWipRequest generates requests for ListWip +func NewListWipRequest(server string, owner string, repository string) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "user", runtime.ParamLocationPath, user) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } @@ -2404,19 +2540,12 @@ func NewGetBranchRequest(server string, user string, repository string, branch s return nil, err } - var pathParam2 string - - pathParam2, err = runtime.StyleParamWithLocation("simple", false, "branch", runtime.ParamLocationPath, branch) - if err != nil { - return nil, err - } - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/%s/%s/branches/%s", pathParam0, pathParam1, pathParam2) + operationPath := fmt.Sprintf("/wip/%s/%s/list", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2486,36 +2615,50 @@ type ClientWithResponsesInterface interface { LogoutWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*LogoutResponse, error) // DeleteObjectWithResponse request - DeleteObjectWithResponse(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) + DeleteObjectWithResponse(ctx context.Context, owner string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) // GetObjectWithResponse request - GetObjectWithResponse(ctx context.Context, user string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*GetObjectResponse, error) + GetObjectWithResponse(ctx context.Context, owner string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*GetObjectResponse, error) // HeadObjectWithResponse request - HeadObjectWithResponse(ctx context.Context, user string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*HeadObjectResponse, error) + HeadObjectWithResponse(ctx context.Context, owner string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*HeadObjectResponse, error) // UploadObjectWithBodyWithResponse request with any body - UploadObjectWithBodyWithResponse(ctx context.Context, user string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) + UploadObjectWithBodyWithResponse(ctx context.Context, owner string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) // DeleteRepositoryWithResponse request - DeleteRepositoryWithResponse(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*DeleteRepositoryResponse, error) + DeleteRepositoryWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*DeleteRepositoryResponse, error) // GetRepositoryWithResponse request - GetRepositoryWithResponse(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*GetRepositoryResponse, error) + GetRepositoryWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*GetRepositoryResponse, error) // UpdateRepositoryWithBodyWithResponse request with any body - UpdateRepositoryWithBodyWithResponse(ctx context.Context, user string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) + UpdateRepositoryWithBodyWithResponse(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) + + UpdateRepositoryWithResponse(ctx context.Context, owner string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) + + // DeleteBranchWithResponse request + DeleteBranchWithResponse(ctx context.Context, owner string, repository string, params *DeleteBranchParams, reqEditors ...RequestEditorFn) (*DeleteBranchResponse, error) - UpdateRepositoryWithResponse(ctx context.Context, user string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) + // GetBranchWithResponse request + GetBranchWithResponse(ctx context.Context, owner string, repository string, params *GetBranchParams, reqEditors ...RequestEditorFn) (*GetBranchResponse, error) + + // CreateBranchWithBodyWithResponse request with any body + CreateBranchWithBodyWithResponse(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) + + CreateBranchWithResponse(ctx context.Context, owner string, repository string, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) + + // ListBranchesWithResponse request + ListBranchesWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*ListBranchesResponse, error) // GetCommitsInRepositoryWithResponse request - GetCommitsInRepositoryWithResponse(ctx context.Context, user string, repository string, params *GetCommitsInRepositoryParams, reqEditors ...RequestEditorFn) (*GetCommitsInRepositoryResponse, error) + GetCommitsInRepositoryWithResponse(ctx context.Context, owner string, repository string, params *GetCommitsInRepositoryParams, reqEditors ...RequestEditorFn) (*GetCommitsInRepositoryResponse, error) // GetCommitDiffWithResponse request - GetCommitDiffWithResponse(ctx context.Context, user string, repository string, basehead string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*GetCommitDiffResponse, error) + GetCommitDiffWithResponse(ctx context.Context, owner string, repository string, basehead string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*GetCommitDiffResponse, error) - // GetEntriesInCommitWithResponse request - GetEntriesInCommitWithResponse(ctx context.Context, user string, repository string, params *GetEntriesInCommitParams, reqEditors ...RequestEditorFn) (*GetEntriesInCommitResponse, error) + // GetEntriesInRefWithResponse request + GetEntriesInRefWithResponse(ctx context.Context, owner string, repository string, params *GetEntriesInRefParams, reqEditors ...RequestEditorFn) (*GetEntriesInRefResponse, error) // GetSetupStateWithResponse request GetSetupStateWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetSetupStateResponse, error) @@ -2525,8 +2668,8 @@ type ClientWithResponsesInterface interface { RegisterWithResponse(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterResponse, error) - // ListRepositoryWithResponse request - ListRepositoryWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListRepositoryResponse, error) + // ListRepositoryOfAuthenticatedUserWithResponse request + ListRepositoryOfAuthenticatedUserWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListRepositoryOfAuthenticatedUserResponse, error) // CreateRepositoryWithBodyWithResponse request with any body CreateRepositoryWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateRepositoryResponse, error) @@ -2536,40 +2679,29 @@ type ClientWithResponsesInterface interface { // GetUserInfoWithResponse request GetUserInfoWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetUserInfoResponse, error) + // ListRepositoryWithResponse request + ListRepositoryWithResponse(ctx context.Context, owner string, reqEditors ...RequestEditorFn) (*ListRepositoryResponse, error) + // GetVersionWithResponse request GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) // DeleteWipWithResponse request - DeleteWipWithResponse(ctx context.Context, repository string, params *DeleteWipParams, reqEditors ...RequestEditorFn) (*DeleteWipResponse, error) + DeleteWipWithResponse(ctx context.Context, owner string, repository string, params *DeleteWipParams, reqEditors ...RequestEditorFn) (*DeleteWipResponse, error) // GetWipWithResponse request - GetWipWithResponse(ctx context.Context, repository string, params *GetWipParams, reqEditors ...RequestEditorFn) (*GetWipResponse, error) + GetWipWithResponse(ctx context.Context, owner string, repository string, params *GetWipParams, reqEditors ...RequestEditorFn) (*GetWipResponse, error) // CreateWipWithResponse request - CreateWipWithResponse(ctx context.Context, repository string, params *CreateWipParams, reqEditors ...RequestEditorFn) (*CreateWipResponse, error) + CreateWipWithResponse(ctx context.Context, owner string, repository string, params *CreateWipParams, reqEditors ...RequestEditorFn) (*CreateWipResponse, error) // GetWipChangesWithResponse request - GetWipChangesWithResponse(ctx context.Context, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*GetWipChangesResponse, error) + GetWipChangesWithResponse(ctx context.Context, owner string, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*GetWipChangesResponse, error) // CommitWipWithResponse request - CommitWipWithResponse(ctx context.Context, repository string, params *CommitWipParams, reqEditors ...RequestEditorFn) (*CommitWipResponse, error) + CommitWipWithResponse(ctx context.Context, owner string, repository string, params *CommitWipParams, reqEditors ...RequestEditorFn) (*CommitWipResponse, error) // ListWipWithResponse request - ListWipWithResponse(ctx context.Context, repository string, reqEditors ...RequestEditorFn) (*ListWipResponse, error) - - // ListBranchesWithResponse request - ListBranchesWithResponse(ctx context.Context, user string, reopsitory string, reqEditors ...RequestEditorFn) (*ListBranchesResponse, error) - - // CreateBranchWithBodyWithResponse request with any body - CreateBranchWithBodyWithResponse(ctx context.Context, user string, reopsitory string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) - - CreateBranchWithResponse(ctx context.Context, user string, reopsitory string, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) - - // DeleteBranchWithResponse request - DeleteBranchWithResponse(ctx context.Context, user string, repository string, branch string, reqEditors ...RequestEditorFn) (*DeleteBranchResponse, error) - - // GetBranchWithResponse request - GetBranchWithResponse(ctx context.Context, user string, repository string, branch string, reqEditors ...RequestEditorFn) (*GetBranchResponse, error) + ListWipWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*ListWipResponse, error) } type LoginResponse struct { @@ -2764,14 +2896,13 @@ func (r UpdateRepositoryResponse) StatusCode() int { return 0 } -type GetCommitsInRepositoryResponse struct { +type DeleteBranchResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *[]Commit } // Status returns HTTPResponse.Status -func (r GetCommitsInRepositoryResponse) Status() string { +func (r DeleteBranchResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2779,21 +2910,21 @@ func (r GetCommitsInRepositoryResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetCommitsInRepositoryResponse) StatusCode() int { +func (r DeleteBranchResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetCommitDiffResponse struct { +type GetBranchResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *[]Change + JSON200 *Ref } // Status returns HTTPResponse.Status -func (r GetCommitDiffResponse) Status() string { +func (r GetBranchResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2801,21 +2932,21 @@ func (r GetCommitDiffResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetCommitDiffResponse) StatusCode() int { +func (r GetBranchResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetEntriesInCommitResponse struct { +type CreateBranchResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *[]TreeEntry + JSON201 *BranchCreation } // Status returns HTTPResponse.Status -func (r GetEntriesInCommitResponse) Status() string { +func (r CreateBranchResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2823,21 +2954,21 @@ func (r GetEntriesInCommitResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetEntriesInCommitResponse) StatusCode() int { +func (r CreateBranchResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetSetupStateResponse struct { +type ListBranchesResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *SetupState + JSON200 *RefList } // Status returns HTTPResponse.Status -func (r GetSetupStateResponse) Status() string { +func (r ListBranchesResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2845,20 +2976,21 @@ func (r GetSetupStateResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetSetupStateResponse) StatusCode() int { +func (r ListBranchesResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type RegisterResponse struct { +type GetCommitsInRepositoryResponse struct { Body []byte HTTPResponse *http.Response + JSON200 *[]Commit } // Status returns HTTPResponse.Status -func (r RegisterResponse) Status() string { +func (r GetCommitsInRepositoryResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2866,21 +2998,21 @@ func (r RegisterResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r RegisterResponse) StatusCode() int { +func (r GetCommitsInRepositoryResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type ListRepositoryResponse struct { +type GetCommitDiffResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *[]Repository + JSON200 *[]Change } // Status returns HTTPResponse.Status -func (r ListRepositoryResponse) Status() string { +func (r GetCommitDiffResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2888,21 +3020,21 @@ func (r ListRepositoryResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r ListRepositoryResponse) StatusCode() int { +func (r GetCommitDiffResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type CreateRepositoryResponse struct { +type GetEntriesInRefResponse struct { Body []byte HTTPResponse *http.Response - JSON201 *[]Repository + JSON200 *[]TreeEntry } // Status returns HTTPResponse.Status -func (r CreateRepositoryResponse) Status() string { +func (r GetEntriesInRefResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2910,21 +3042,21 @@ func (r CreateRepositoryResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r CreateRepositoryResponse) StatusCode() int { +func (r GetEntriesInRefResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetUserInfoResponse struct { +type GetSetupStateResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *UserInfo + JSON200 *SetupState } // Status returns HTTPResponse.Status -func (r GetUserInfoResponse) Status() string { +func (r GetSetupStateResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2932,21 +3064,20 @@ func (r GetUserInfoResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetUserInfoResponse) StatusCode() int { +func (r GetSetupStateResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetVersionResponse struct { +type RegisterResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *VersionResult } // Status returns HTTPResponse.Status -func (r GetVersionResponse) Status() string { +func (r RegisterResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2954,20 +3085,21 @@ func (r GetVersionResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetVersionResponse) StatusCode() int { +func (r RegisterResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type DeleteWipResponse struct { +type ListRepositoryOfAuthenticatedUserResponse struct { Body []byte HTTPResponse *http.Response + JSON200 *[]Repository } // Status returns HTTPResponse.Status -func (r DeleteWipResponse) Status() string { +func (r ListRepositoryOfAuthenticatedUserResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2975,21 +3107,21 @@ func (r DeleteWipResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r DeleteWipResponse) StatusCode() int { +func (r ListRepositoryOfAuthenticatedUserResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetWipResponse struct { +type CreateRepositoryResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *Wip + JSON201 *[]Repository } // Status returns HTTPResponse.Status -func (r GetWipResponse) Status() string { +func (r CreateRepositoryResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -2997,21 +3129,21 @@ func (r GetWipResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetWipResponse) StatusCode() int { +func (r CreateRepositoryResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type CreateWipResponse struct { +type GetUserInfoResponse struct { Body []byte HTTPResponse *http.Response - JSON201 *Wip + JSON200 *UserInfo } // Status returns HTTPResponse.Status -func (r CreateWipResponse) Status() string { +func (r GetUserInfoResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3019,21 +3151,21 @@ func (r CreateWipResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r CreateWipResponse) StatusCode() int { +func (r GetUserInfoResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetWipChangesResponse struct { +type ListRepositoryResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *Change + JSON200 *[]Repository } // Status returns HTTPResponse.Status -func (r GetWipChangesResponse) Status() string { +func (r ListRepositoryResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3041,21 +3173,21 @@ func (r GetWipChangesResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetWipChangesResponse) StatusCode() int { +func (r ListRepositoryResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type CommitWipResponse struct { +type GetVersionResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *Wip + JSON200 *VersionResult } // Status returns HTTPResponse.Status -func (r CommitWipResponse) Status() string { +func (r GetVersionResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3063,21 +3195,20 @@ func (r CommitWipResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r CommitWipResponse) StatusCode() int { +func (r GetVersionResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type ListWipResponse struct { +type DeleteWipResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *[]Wip } // Status returns HTTPResponse.Status -func (r ListWipResponse) Status() string { +func (r DeleteWipResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3085,21 +3216,21 @@ func (r ListWipResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r ListWipResponse) StatusCode() int { +func (r DeleteWipResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type ListBranchesResponse struct { +type GetWipResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *RefList + JSON200 *Wip } // Status returns HTTPResponse.Status -func (r ListBranchesResponse) Status() string { +func (r GetWipResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3107,20 +3238,21 @@ func (r ListBranchesResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r ListBranchesResponse) StatusCode() int { +func (r GetWipResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type CreateBranchResponse struct { +type CreateWipResponse struct { Body []byte HTTPResponse *http.Response + JSON201 *Wip } // Status returns HTTPResponse.Status -func (r CreateBranchResponse) Status() string { +func (r CreateWipResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3128,20 +3260,21 @@ func (r CreateBranchResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r CreateBranchResponse) StatusCode() int { +func (r CreateWipResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type DeleteBranchResponse struct { +type GetWipChangesResponse struct { Body []byte HTTPResponse *http.Response + JSON200 *Change } // Status returns HTTPResponse.Status -func (r DeleteBranchResponse) Status() string { +func (r GetWipChangesResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3149,21 +3282,21 @@ func (r DeleteBranchResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r DeleteBranchResponse) StatusCode() int { +func (r GetWipChangesResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetBranchResponse struct { +type CommitWipResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *Ref + JSON200 *Wip } // Status returns HTTPResponse.Status -func (r GetBranchResponse) Status() string { +func (r CommitWipResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3171,7 +3304,29 @@ func (r GetBranchResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetBranchResponse) StatusCode() int { +func (r CommitWipResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type ListWipResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]Wip +} + +// Status returns HTTPResponse.Status +func (r ListWipResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ListWipResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -3205,8 +3360,8 @@ func (c *ClientWithResponses) LogoutWithResponse(ctx context.Context, reqEditors } // DeleteObjectWithResponse request returning *DeleteObjectResponse -func (c *ClientWithResponses) DeleteObjectWithResponse(ctx context.Context, user string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) { - rsp, err := c.DeleteObject(ctx, user, repository, params, reqEditors...) +func (c *ClientWithResponses) DeleteObjectWithResponse(ctx context.Context, owner string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) { + rsp, err := c.DeleteObject(ctx, owner, repository, params, reqEditors...) if err != nil { return nil, err } @@ -3214,8 +3369,8 @@ func (c *ClientWithResponses) DeleteObjectWithResponse(ctx context.Context, user } // GetObjectWithResponse request returning *GetObjectResponse -func (c *ClientWithResponses) GetObjectWithResponse(ctx context.Context, user string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*GetObjectResponse, error) { - rsp, err := c.GetObject(ctx, user, repository, params, reqEditors...) +func (c *ClientWithResponses) GetObjectWithResponse(ctx context.Context, owner string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*GetObjectResponse, error) { + rsp, err := c.GetObject(ctx, owner, repository, params, reqEditors...) if err != nil { return nil, err } @@ -3223,8 +3378,8 @@ func (c *ClientWithResponses) GetObjectWithResponse(ctx context.Context, user st } // HeadObjectWithResponse request returning *HeadObjectResponse -func (c *ClientWithResponses) HeadObjectWithResponse(ctx context.Context, user string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*HeadObjectResponse, error) { - rsp, err := c.HeadObject(ctx, user, repository, params, reqEditors...) +func (c *ClientWithResponses) HeadObjectWithResponse(ctx context.Context, owner string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*HeadObjectResponse, error) { + rsp, err := c.HeadObject(ctx, owner, repository, params, reqEditors...) if err != nil { return nil, err } @@ -3232,8 +3387,8 @@ func (c *ClientWithResponses) HeadObjectWithResponse(ctx context.Context, user s } // UploadObjectWithBodyWithResponse request with arbitrary body returning *UploadObjectResponse -func (c *ClientWithResponses) UploadObjectWithBodyWithResponse(ctx context.Context, user string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) { - rsp, err := c.UploadObjectWithBody(ctx, user, repository, params, contentType, body, reqEditors...) +func (c *ClientWithResponses) UploadObjectWithBodyWithResponse(ctx context.Context, owner string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) { + rsp, err := c.UploadObjectWithBody(ctx, owner, repository, params, contentType, body, reqEditors...) if err != nil { return nil, err } @@ -3241,43 +3396,87 @@ func (c *ClientWithResponses) UploadObjectWithBodyWithResponse(ctx context.Conte } // DeleteRepositoryWithResponse request returning *DeleteRepositoryResponse -func (c *ClientWithResponses) DeleteRepositoryWithResponse(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*DeleteRepositoryResponse, error) { - rsp, err := c.DeleteRepository(ctx, user, repository, reqEditors...) +func (c *ClientWithResponses) DeleteRepositoryWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*DeleteRepositoryResponse, error) { + rsp, err := c.DeleteRepository(ctx, owner, repository, reqEditors...) if err != nil { return nil, err } return ParseDeleteRepositoryResponse(rsp) } -// GetRepositoryWithResponse request returning *GetRepositoryResponse -func (c *ClientWithResponses) GetRepositoryWithResponse(ctx context.Context, user string, repository string, reqEditors ...RequestEditorFn) (*GetRepositoryResponse, error) { - rsp, err := c.GetRepository(ctx, user, repository, reqEditors...) +// GetRepositoryWithResponse request returning *GetRepositoryResponse +func (c *ClientWithResponses) GetRepositoryWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*GetRepositoryResponse, error) { + rsp, err := c.GetRepository(ctx, owner, repository, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetRepositoryResponse(rsp) +} + +// UpdateRepositoryWithBodyWithResponse request with arbitrary body returning *UpdateRepositoryResponse +func (c *ClientWithResponses) UpdateRepositoryWithBodyWithResponse(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) { + rsp, err := c.UpdateRepositoryWithBody(ctx, owner, repository, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateRepositoryResponse(rsp) +} + +func (c *ClientWithResponses) UpdateRepositoryWithResponse(ctx context.Context, owner string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) { + rsp, err := c.UpdateRepository(ctx, owner, repository, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateRepositoryResponse(rsp) +} + +// DeleteBranchWithResponse request returning *DeleteBranchResponse +func (c *ClientWithResponses) DeleteBranchWithResponse(ctx context.Context, owner string, repository string, params *DeleteBranchParams, reqEditors ...RequestEditorFn) (*DeleteBranchResponse, error) { + rsp, err := c.DeleteBranch(ctx, owner, repository, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteBranchResponse(rsp) +} + +// GetBranchWithResponse request returning *GetBranchResponse +func (c *ClientWithResponses) GetBranchWithResponse(ctx context.Context, owner string, repository string, params *GetBranchParams, reqEditors ...RequestEditorFn) (*GetBranchResponse, error) { + rsp, err := c.GetBranch(ctx, owner, repository, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetBranchResponse(rsp) +} + +// CreateBranchWithBodyWithResponse request with arbitrary body returning *CreateBranchResponse +func (c *ClientWithResponses) CreateBranchWithBodyWithResponse(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) { + rsp, err := c.CreateBranchWithBody(ctx, owner, repository, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseGetRepositoryResponse(rsp) + return ParseCreateBranchResponse(rsp) } -// UpdateRepositoryWithBodyWithResponse request with arbitrary body returning *UpdateRepositoryResponse -func (c *ClientWithResponses) UpdateRepositoryWithBodyWithResponse(ctx context.Context, user string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) { - rsp, err := c.UpdateRepositoryWithBody(ctx, user, repository, contentType, body, reqEditors...) +func (c *ClientWithResponses) CreateBranchWithResponse(ctx context.Context, owner string, repository string, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) { + rsp, err := c.CreateBranch(ctx, owner, repository, body, reqEditors...) if err != nil { return nil, err } - return ParseUpdateRepositoryResponse(rsp) + return ParseCreateBranchResponse(rsp) } -func (c *ClientWithResponses) UpdateRepositoryWithResponse(ctx context.Context, user string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) { - rsp, err := c.UpdateRepository(ctx, user, repository, body, reqEditors...) +// ListBranchesWithResponse request returning *ListBranchesResponse +func (c *ClientWithResponses) ListBranchesWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*ListBranchesResponse, error) { + rsp, err := c.ListBranches(ctx, owner, repository, reqEditors...) if err != nil { return nil, err } - return ParseUpdateRepositoryResponse(rsp) + return ParseListBranchesResponse(rsp) } // GetCommitsInRepositoryWithResponse request returning *GetCommitsInRepositoryResponse -func (c *ClientWithResponses) GetCommitsInRepositoryWithResponse(ctx context.Context, user string, repository string, params *GetCommitsInRepositoryParams, reqEditors ...RequestEditorFn) (*GetCommitsInRepositoryResponse, error) { - rsp, err := c.GetCommitsInRepository(ctx, user, repository, params, reqEditors...) +func (c *ClientWithResponses) GetCommitsInRepositoryWithResponse(ctx context.Context, owner string, repository string, params *GetCommitsInRepositoryParams, reqEditors ...RequestEditorFn) (*GetCommitsInRepositoryResponse, error) { + rsp, err := c.GetCommitsInRepository(ctx, owner, repository, params, reqEditors...) if err != nil { return nil, err } @@ -3285,21 +3484,21 @@ func (c *ClientWithResponses) GetCommitsInRepositoryWithResponse(ctx context.Con } // GetCommitDiffWithResponse request returning *GetCommitDiffResponse -func (c *ClientWithResponses) GetCommitDiffWithResponse(ctx context.Context, user string, repository string, basehead string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*GetCommitDiffResponse, error) { - rsp, err := c.GetCommitDiff(ctx, user, repository, basehead, params, reqEditors...) +func (c *ClientWithResponses) GetCommitDiffWithResponse(ctx context.Context, owner string, repository string, basehead string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*GetCommitDiffResponse, error) { + rsp, err := c.GetCommitDiff(ctx, owner, repository, basehead, params, reqEditors...) if err != nil { return nil, err } return ParseGetCommitDiffResponse(rsp) } -// GetEntriesInCommitWithResponse request returning *GetEntriesInCommitResponse -func (c *ClientWithResponses) GetEntriesInCommitWithResponse(ctx context.Context, user string, repository string, params *GetEntriesInCommitParams, reqEditors ...RequestEditorFn) (*GetEntriesInCommitResponse, error) { - rsp, err := c.GetEntriesInCommit(ctx, user, repository, params, reqEditors...) +// GetEntriesInRefWithResponse request returning *GetEntriesInRefResponse +func (c *ClientWithResponses) GetEntriesInRefWithResponse(ctx context.Context, owner string, repository string, params *GetEntriesInRefParams, reqEditors ...RequestEditorFn) (*GetEntriesInRefResponse, error) { + rsp, err := c.GetEntriesInRef(ctx, owner, repository, params, reqEditors...) if err != nil { return nil, err } - return ParseGetEntriesInCommitResponse(rsp) + return ParseGetEntriesInRefResponse(rsp) } // GetSetupStateWithResponse request returning *GetSetupStateResponse @@ -3328,13 +3527,13 @@ func (c *ClientWithResponses) RegisterWithResponse(ctx context.Context, body Reg return ParseRegisterResponse(rsp) } -// ListRepositoryWithResponse request returning *ListRepositoryResponse -func (c *ClientWithResponses) ListRepositoryWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListRepositoryResponse, error) { - rsp, err := c.ListRepository(ctx, reqEditors...) +// ListRepositoryOfAuthenticatedUserWithResponse request returning *ListRepositoryOfAuthenticatedUserResponse +func (c *ClientWithResponses) ListRepositoryOfAuthenticatedUserWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListRepositoryOfAuthenticatedUserResponse, error) { + rsp, err := c.ListRepositoryOfAuthenticatedUser(ctx, reqEditors...) if err != nil { return nil, err } - return ParseListRepositoryResponse(rsp) + return ParseListRepositoryOfAuthenticatedUserResponse(rsp) } // CreateRepositoryWithBodyWithResponse request with arbitrary body returning *CreateRepositoryResponse @@ -3363,6 +3562,15 @@ func (c *ClientWithResponses) GetUserInfoWithResponse(ctx context.Context, reqEd return ParseGetUserInfoResponse(rsp) } +// ListRepositoryWithResponse request returning *ListRepositoryResponse +func (c *ClientWithResponses) ListRepositoryWithResponse(ctx context.Context, owner string, reqEditors ...RequestEditorFn) (*ListRepositoryResponse, error) { + rsp, err := c.ListRepository(ctx, owner, reqEditors...) + if err != nil { + return nil, err + } + return ParseListRepositoryResponse(rsp) +} + // GetVersionWithResponse request returning *GetVersionResponse func (c *ClientWithResponses) GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) { rsp, err := c.GetVersion(ctx, reqEditors...) @@ -3373,8 +3581,8 @@ func (c *ClientWithResponses) GetVersionWithResponse(ctx context.Context, reqEdi } // DeleteWipWithResponse request returning *DeleteWipResponse -func (c *ClientWithResponses) DeleteWipWithResponse(ctx context.Context, repository string, params *DeleteWipParams, reqEditors ...RequestEditorFn) (*DeleteWipResponse, error) { - rsp, err := c.DeleteWip(ctx, repository, params, reqEditors...) +func (c *ClientWithResponses) DeleteWipWithResponse(ctx context.Context, owner string, repository string, params *DeleteWipParams, reqEditors ...RequestEditorFn) (*DeleteWipResponse, error) { + rsp, err := c.DeleteWip(ctx, owner, repository, params, reqEditors...) if err != nil { return nil, err } @@ -3382,8 +3590,8 @@ func (c *ClientWithResponses) DeleteWipWithResponse(ctx context.Context, reposit } // GetWipWithResponse request returning *GetWipResponse -func (c *ClientWithResponses) GetWipWithResponse(ctx context.Context, repository string, params *GetWipParams, reqEditors ...RequestEditorFn) (*GetWipResponse, error) { - rsp, err := c.GetWip(ctx, repository, params, reqEditors...) +func (c *ClientWithResponses) GetWipWithResponse(ctx context.Context, owner string, repository string, params *GetWipParams, reqEditors ...RequestEditorFn) (*GetWipResponse, error) { + rsp, err := c.GetWip(ctx, owner, repository, params, reqEditors...) if err != nil { return nil, err } @@ -3391,8 +3599,8 @@ func (c *ClientWithResponses) GetWipWithResponse(ctx context.Context, repository } // CreateWipWithResponse request returning *CreateWipResponse -func (c *ClientWithResponses) CreateWipWithResponse(ctx context.Context, repository string, params *CreateWipParams, reqEditors ...RequestEditorFn) (*CreateWipResponse, error) { - rsp, err := c.CreateWip(ctx, repository, params, reqEditors...) +func (c *ClientWithResponses) CreateWipWithResponse(ctx context.Context, owner string, repository string, params *CreateWipParams, reqEditors ...RequestEditorFn) (*CreateWipResponse, error) { + rsp, err := c.CreateWip(ctx, owner, repository, params, reqEditors...) if err != nil { return nil, err } @@ -3400,8 +3608,8 @@ func (c *ClientWithResponses) CreateWipWithResponse(ctx context.Context, reposit } // GetWipChangesWithResponse request returning *GetWipChangesResponse -func (c *ClientWithResponses) GetWipChangesWithResponse(ctx context.Context, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*GetWipChangesResponse, error) { - rsp, err := c.GetWipChanges(ctx, repository, params, reqEditors...) +func (c *ClientWithResponses) GetWipChangesWithResponse(ctx context.Context, owner string, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*GetWipChangesResponse, error) { + rsp, err := c.GetWipChanges(ctx, owner, repository, params, reqEditors...) if err != nil { return nil, err } @@ -3409,8 +3617,8 @@ func (c *ClientWithResponses) GetWipChangesWithResponse(ctx context.Context, rep } // CommitWipWithResponse request returning *CommitWipResponse -func (c *ClientWithResponses) CommitWipWithResponse(ctx context.Context, repository string, params *CommitWipParams, reqEditors ...RequestEditorFn) (*CommitWipResponse, error) { - rsp, err := c.CommitWip(ctx, repository, params, reqEditors...) +func (c *ClientWithResponses) CommitWipWithResponse(ctx context.Context, owner string, repository string, params *CommitWipParams, reqEditors ...RequestEditorFn) (*CommitWipResponse, error) { + rsp, err := c.CommitWip(ctx, owner, repository, params, reqEditors...) if err != nil { return nil, err } @@ -3418,58 +3626,14 @@ func (c *ClientWithResponses) CommitWipWithResponse(ctx context.Context, reposit } // ListWipWithResponse request returning *ListWipResponse -func (c *ClientWithResponses) ListWipWithResponse(ctx context.Context, repository string, reqEditors ...RequestEditorFn) (*ListWipResponse, error) { - rsp, err := c.ListWip(ctx, repository, reqEditors...) +func (c *ClientWithResponses) ListWipWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*ListWipResponse, error) { + rsp, err := c.ListWip(ctx, owner, repository, reqEditors...) if err != nil { return nil, err } return ParseListWipResponse(rsp) } -// ListBranchesWithResponse request returning *ListBranchesResponse -func (c *ClientWithResponses) ListBranchesWithResponse(ctx context.Context, user string, reopsitory string, reqEditors ...RequestEditorFn) (*ListBranchesResponse, error) { - rsp, err := c.ListBranches(ctx, user, reopsitory, reqEditors...) - if err != nil { - return nil, err - } - return ParseListBranchesResponse(rsp) -} - -// CreateBranchWithBodyWithResponse request with arbitrary body returning *CreateBranchResponse -func (c *ClientWithResponses) CreateBranchWithBodyWithResponse(ctx context.Context, user string, reopsitory string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) { - rsp, err := c.CreateBranchWithBody(ctx, user, reopsitory, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseCreateBranchResponse(rsp) -} - -func (c *ClientWithResponses) CreateBranchWithResponse(ctx context.Context, user string, reopsitory string, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) { - rsp, err := c.CreateBranch(ctx, user, reopsitory, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseCreateBranchResponse(rsp) -} - -// DeleteBranchWithResponse request returning *DeleteBranchResponse -func (c *ClientWithResponses) DeleteBranchWithResponse(ctx context.Context, user string, repository string, branch string, reqEditors ...RequestEditorFn) (*DeleteBranchResponse, error) { - rsp, err := c.DeleteBranch(ctx, user, repository, branch, reqEditors...) - if err != nil { - return nil, err - } - return ParseDeleteBranchResponse(rsp) -} - -// GetBranchWithResponse request returning *GetBranchResponse -func (c *ClientWithResponses) GetBranchWithResponse(ctx context.Context, user string, repository string, branch string, reqEditors ...RequestEditorFn) (*GetBranchResponse, error) { - rsp, err := c.GetBranch(ctx, user, repository, branch, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetBranchResponse(rsp) -} - // ParseLoginResponse parses an HTTP response from a LoginWithResponse call func ParseLoginResponse(rsp *http.Response) (*LoginResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -3644,48 +3808,38 @@ func ParseUpdateRepositoryResponse(rsp *http.Response) (*UpdateRepositoryRespons return response, nil } -// ParseGetCommitsInRepositoryResponse parses an HTTP response from a GetCommitsInRepositoryWithResponse call -func ParseGetCommitsInRepositoryResponse(rsp *http.Response) (*GetCommitsInRepositoryResponse, error) { +// ParseDeleteBranchResponse parses an HTTP response from a DeleteBranchWithResponse call +func ParseDeleteBranchResponse(rsp *http.Response) (*DeleteBranchResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetCommitsInRepositoryResponse{ + response := &DeleteBranchResponse{ Body: bodyBytes, HTTPResponse: rsp, } - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []Commit - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - } - return response, nil } -// ParseGetCommitDiffResponse parses an HTTP response from a GetCommitDiffWithResponse call -func ParseGetCommitDiffResponse(rsp *http.Response) (*GetCommitDiffResponse, error) { +// ParseGetBranchResponse parses an HTTP response from a GetBranchWithResponse call +func ParseGetBranchResponse(rsp *http.Response) (*GetBranchResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetCommitDiffResponse{ + response := &GetBranchResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []Change + var dest Ref if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -3696,48 +3850,48 @@ func ParseGetCommitDiffResponse(rsp *http.Response) (*GetCommitDiffResponse, err return response, nil } -// ParseGetEntriesInCommitResponse parses an HTTP response from a GetEntriesInCommitWithResponse call -func ParseGetEntriesInCommitResponse(rsp *http.Response) (*GetEntriesInCommitResponse, error) { +// ParseCreateBranchResponse parses an HTTP response from a CreateBranchWithResponse call +func ParseCreateBranchResponse(rsp *http.Response) (*CreateBranchResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetEntriesInCommitResponse{ + response := &CreateBranchResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []TreeEntry + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest BranchCreation if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON200 = &dest + response.JSON201 = &dest } return response, nil } -// ParseGetSetupStateResponse parses an HTTP response from a GetSetupStateWithResponse call -func ParseGetSetupStateResponse(rsp *http.Response) (*GetSetupStateResponse, error) { +// ParseListBranchesResponse parses an HTTP response from a ListBranchesWithResponse call +func ParseListBranchesResponse(rsp *http.Response) (*ListBranchesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetSetupStateResponse{ + response := &ListBranchesResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest SetupState + var dest RefList if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -3748,38 +3902,22 @@ func ParseGetSetupStateResponse(rsp *http.Response) (*GetSetupStateResponse, err return response, nil } -// ParseRegisterResponse parses an HTTP response from a RegisterWithResponse call -func ParseRegisterResponse(rsp *http.Response) (*RegisterResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &RegisterResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - return response, nil -} - -// ParseListRepositoryResponse parses an HTTP response from a ListRepositoryWithResponse call -func ParseListRepositoryResponse(rsp *http.Response) (*ListRepositoryResponse, error) { +// ParseGetCommitsInRepositoryResponse parses an HTTP response from a GetCommitsInRepositoryWithResponse call +func ParseGetCommitsInRepositoryResponse(rsp *http.Response) (*GetCommitsInRepositoryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &ListRepositoryResponse{ + response := &GetCommitsInRepositoryResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []Repository + var dest []Commit if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -3790,48 +3928,48 @@ func ParseListRepositoryResponse(rsp *http.Response) (*ListRepositoryResponse, e return response, nil } -// ParseCreateRepositoryResponse parses an HTTP response from a CreateRepositoryWithResponse call -func ParseCreateRepositoryResponse(rsp *http.Response) (*CreateRepositoryResponse, error) { +// ParseGetCommitDiffResponse parses an HTTP response from a GetCommitDiffWithResponse call +func ParseGetCommitDiffResponse(rsp *http.Response) (*GetCommitDiffResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CreateRepositoryResponse{ + response := &GetCommitDiffResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest []Repository + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []Change if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON201 = &dest + response.JSON200 = &dest } return response, nil } -// ParseGetUserInfoResponse parses an HTTP response from a GetUserInfoWithResponse call -func ParseGetUserInfoResponse(rsp *http.Response) (*GetUserInfoResponse, error) { +// ParseGetEntriesInRefResponse parses an HTTP response from a GetEntriesInRefWithResponse call +func ParseGetEntriesInRefResponse(rsp *http.Response) (*GetEntriesInRefResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetUserInfoResponse{ + response := &GetEntriesInRefResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest UserInfo + var dest []TreeEntry if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -3842,22 +3980,22 @@ func ParseGetUserInfoResponse(rsp *http.Response) (*GetUserInfoResponse, error) return response, nil } -// ParseGetVersionResponse parses an HTTP response from a GetVersionWithResponse call -func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) { +// ParseGetSetupStateResponse parses an HTTP response from a GetSetupStateWithResponse call +func ParseGetSetupStateResponse(rsp *http.Response) (*GetSetupStateResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetVersionResponse{ + response := &GetSetupStateResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest VersionResult + var dest SetupState if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -3868,15 +4006,15 @@ func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) { return response, nil } -// ParseDeleteWipResponse parses an HTTP response from a DeleteWipWithResponse call -func ParseDeleteWipResponse(rsp *http.Response) (*DeleteWipResponse, error) { +// ParseRegisterResponse parses an HTTP response from a RegisterWithResponse call +func ParseRegisterResponse(rsp *http.Response) (*RegisterResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &DeleteWipResponse{ + response := &RegisterResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -3884,22 +4022,22 @@ func ParseDeleteWipResponse(rsp *http.Response) (*DeleteWipResponse, error) { return response, nil } -// ParseGetWipResponse parses an HTTP response from a GetWipWithResponse call -func ParseGetWipResponse(rsp *http.Response) (*GetWipResponse, error) { +// ParseListRepositoryOfAuthenticatedUserResponse parses an HTTP response from a ListRepositoryOfAuthenticatedUserWithResponse call +func ParseListRepositoryOfAuthenticatedUserResponse(rsp *http.Response) (*ListRepositoryOfAuthenticatedUserResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetWipResponse{ + response := &ListRepositoryOfAuthenticatedUserResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest Wip + var dest []Repository if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -3910,22 +4048,22 @@ func ParseGetWipResponse(rsp *http.Response) (*GetWipResponse, error) { return response, nil } -// ParseCreateWipResponse parses an HTTP response from a CreateWipWithResponse call -func ParseCreateWipResponse(rsp *http.Response) (*CreateWipResponse, error) { +// ParseCreateRepositoryResponse parses an HTTP response from a CreateRepositoryWithResponse call +func ParseCreateRepositoryResponse(rsp *http.Response) (*CreateRepositoryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CreateWipResponse{ + response := &CreateRepositoryResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest Wip + var dest []Repository if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -3936,22 +4074,22 @@ func ParseCreateWipResponse(rsp *http.Response) (*CreateWipResponse, error) { return response, nil } -// ParseGetWipChangesResponse parses an HTTP response from a GetWipChangesWithResponse call -func ParseGetWipChangesResponse(rsp *http.Response) (*GetWipChangesResponse, error) { +// ParseGetUserInfoResponse parses an HTTP response from a GetUserInfoWithResponse call +func ParseGetUserInfoResponse(rsp *http.Response) (*GetUserInfoResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetWipChangesResponse{ + response := &GetUserInfoResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest Change + var dest UserInfo if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -3962,22 +4100,22 @@ func ParseGetWipChangesResponse(rsp *http.Response) (*GetWipChangesResponse, err return response, nil } -// ParseCommitWipResponse parses an HTTP response from a CommitWipWithResponse call -func ParseCommitWipResponse(rsp *http.Response) (*CommitWipResponse, error) { +// ParseListRepositoryResponse parses an HTTP response from a ListRepositoryWithResponse call +func ParseListRepositoryResponse(rsp *http.Response) (*ListRepositoryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CommitWipResponse{ + response := &ListRepositoryResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest Wip + var dest []Repository if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -3988,22 +4126,22 @@ func ParseCommitWipResponse(rsp *http.Response) (*CommitWipResponse, error) { return response, nil } -// ParseListWipResponse parses an HTTP response from a ListWipWithResponse call -func ParseListWipResponse(rsp *http.Response) (*ListWipResponse, error) { +// ParseGetVersionResponse parses an HTTP response from a GetVersionWithResponse call +func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &ListWipResponse{ + response := &GetVersionResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []Wip + var dest VersionResult if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -4014,22 +4152,38 @@ func ParseListWipResponse(rsp *http.Response) (*ListWipResponse, error) { return response, nil } -// ParseListBranchesResponse parses an HTTP response from a ListBranchesWithResponse call -func ParseListBranchesResponse(rsp *http.Response) (*ListBranchesResponse, error) { +// ParseDeleteWipResponse parses an HTTP response from a DeleteWipWithResponse call +func ParseDeleteWipResponse(rsp *http.Response) (*DeleteWipResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &ListBranchesResponse{ + response := &DeleteWipResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetWipResponse parses an HTTP response from a GetWipWithResponse call +func ParseGetWipResponse(rsp *http.Response) (*GetWipResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetWipResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest RefList + var dest Wip if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -4040,54 +4194,100 @@ func ParseListBranchesResponse(rsp *http.Response) (*ListBranchesResponse, error return response, nil } -// ParseCreateBranchResponse parses an HTTP response from a CreateBranchWithResponse call -func ParseCreateBranchResponse(rsp *http.Response) (*CreateBranchResponse, error) { +// ParseCreateWipResponse parses an HTTP response from a CreateWipWithResponse call +func ParseCreateWipResponse(rsp *http.Response) (*CreateWipResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CreateBranchResponse{ + response := &CreateWipResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest Wip + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil +} + +// ParseGetWipChangesResponse parses an HTTP response from a GetWipChangesWithResponse call +func ParseGetWipChangesResponse(rsp *http.Response) (*GetWipChangesResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetWipChangesResponse{ Body: bodyBytes, HTTPResponse: rsp, } + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Change + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + return response, nil } -// ParseDeleteBranchResponse parses an HTTP response from a DeleteBranchWithResponse call -func ParseDeleteBranchResponse(rsp *http.Response) (*DeleteBranchResponse, error) { +// ParseCommitWipResponse parses an HTTP response from a CommitWipWithResponse call +func ParseCommitWipResponse(rsp *http.Response) (*CommitWipResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &DeleteBranchResponse{ + response := &CommitWipResponse{ Body: bodyBytes, HTTPResponse: rsp, } + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Wip + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + return response, nil } -// ParseGetBranchResponse parses an HTTP response from a GetBranchWithResponse call -func ParseGetBranchResponse(rsp *http.Response) (*GetBranchResponse, error) { +// ParseListWipResponse parses an HTTP response from a ListWipWithResponse call +func ParseListWipResponse(rsp *http.Response) (*ListWipResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetBranchResponse{ + response := &ListWipResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest Ref + var dest []Wip if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -4107,35 +4307,47 @@ type ServerInterface interface { // (POST /auth/logout) Logout(ctx context.Context, w *JiaozifsResponse, r *http.Request) // delete object. Missing objects will not return a NotFound error. - // (DELETE /object/{user}/{repository}) - DeleteObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params DeleteObjectParams) + // (DELETE /object/{owner}/{repository}) + DeleteObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteObjectParams) // get object content - // (GET /object/{user}/{repository}) - GetObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params GetObjectParams) + // (GET /object/{owner}/{repository}) + GetObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetObjectParams) // check if object exists - // (HEAD /object/{user}/{repository}) - HeadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params HeadObjectParams) + // (HEAD /object/{owner}/{repository}) + HeadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params HeadObjectParams) - // (POST /object/{user}/{repository}) - UploadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params UploadObjectParams) + // (POST /object/{owner}/{repository}) + UploadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params UploadObjectParams) // delete repository - // (DELETE /repos/{user}/{repository}) - DeleteRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string) + // (DELETE /repos/{owner}/{repository}) + DeleteRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) // get repository - // (GET /repos/{user}/{repository}) - GetRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string) + // (GET /repos/{owner}/{repository}) + GetRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) // update repository - // (POST /repos/{user}/{repository}) - UpdateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateRepositoryJSONRequestBody, user string, repository string) + // (POST /repos/{owner}/{repository}) + UpdateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateRepositoryJSONRequestBody, owner string, repository string) + // delete branch + // (DELETE /repos/{owner}/{repository}/branch) + DeleteBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteBranchParams) + // get branch + // (GET /repos/{owner}/{repository}/branch) + GetBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetBranchParams) + // create branch + // (POST /repos/{owner}/{repository}/branch) + CreateBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateBranchJSONRequestBody, owner string, repository string) + // list branches + // (GET /repos/{owner}/{repository}/branches) + ListBranches(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) // get commits in repository - // (GET /repos/{user}/{repository}/commits) - GetCommitsInRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params GetCommitsInRepositoryParams) + // (GET /repos/{owner}/{repository}/commits) + GetCommitsInRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetCommitsInRepositoryParams) // get commit differences - // (GET /repos/{user}/{repository}/compare/{basehead}) - GetCommitDiff(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, basehead string, params GetCommitDiffParams) - // list entries in commit - // (GET /repos/{user}/{repository}/contents/) - GetEntriesInCommit(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params GetEntriesInCommitParams) + // (GET /repos/{owner}/{repository}/compare/{basehead}) + GetCommitDiff(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, basehead string, params GetCommitDiffParams) + // list entries in ref + // (GET /repos/{owner}/{repository}/contents/) + GetEntriesInRef(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetEntriesInRefParams) // check if jiaozifs setup // (GET /setup) GetSetupState(ctx context.Context, w *JiaozifsResponse, r *http.Request) @@ -4144,46 +4356,37 @@ type ServerInterface interface { Register(ctx context.Context, w *JiaozifsResponse, r *http.Request, body RegisterJSONRequestBody) // list repository // (GET /users/repos) - ListRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request) + ListRepositoryOfAuthenticatedUser(ctx context.Context, w *JiaozifsResponse, r *http.Request) // create repository // (POST /users/repos) CreateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateRepositoryJSONRequestBody) // get information of the currently logged-in user // (GET /users/user) GetUserInfo(ctx context.Context, w *JiaozifsResponse, r *http.Request) + // list repository in specific owner + // (GET /users/{owner}/repos) + ListRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string) // return program and runtime version // (GET /version) GetVersion(ctx context.Context, w *JiaozifsResponse, r *http.Request) // remove working in process - // (DELETE /wip/{repository}) - DeleteWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string, params DeleteWipParams) + // (DELETE /wip/{owner}/{repository}) + DeleteWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteWipParams) // get working in process - // (GET /wip/{repository}) - GetWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string, params GetWipParams) + // (GET /wip/{owner}/{repository}) + GetWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetWipParams) // create working in process - // (POST /wip/{repository}) - CreateWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string, params CreateWipParams) + // (POST /wip/{owner}/{repository}) + CreateWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params CreateWipParams) // get working in process changes - // (GET /wip/{repository}/changes) - GetWipChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string, params GetWipChangesParams) + // (GET /wip/{owner}/{repository}/changes) + GetWipChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetWipChangesParams) // commit working in process to branch - // (POST /wip/{repository}/commit) - CommitWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string, params CommitWipParams) + // (POST /wip/{owner}/{repository}/commit) + CommitWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params CommitWipParams) // list wip in specific project and user - // (GET /wip/{repository}/list) - ListWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string) - // list branches - // (GET /{user}/{reopsitory}/branches) - ListBranches(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, reopsitory string) - // create branch - // (POST /{user}/{reopsitory}/branches) - CreateBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateBranchJSONRequestBody, user string, reopsitory string) - // delete branch - // (DELETE /{user}/{repository}/branches/{branch}) - DeleteBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, branch string) - // get branch - // (GET /{user}/{repository}/branches/{branch}) - GetBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, branch string) + // (GET /wip/{owner}/{repository}/list) + ListWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) } // Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. @@ -4203,61 +4406,85 @@ func (_ Unimplemented) Logout(ctx context.Context, w *JiaozifsResponse, r *http. } // delete object. Missing objects will not return a NotFound error. -// (DELETE /object/{user}/{repository}) -func (_ Unimplemented) DeleteObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params DeleteObjectParams) { +// (DELETE /object/{owner}/{repository}) +func (_ Unimplemented) DeleteObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteObjectParams) { w.WriteHeader(http.StatusNotImplemented) } // get object content -// (GET /object/{user}/{repository}) -func (_ Unimplemented) GetObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params GetObjectParams) { +// (GET /object/{owner}/{repository}) +func (_ Unimplemented) GetObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetObjectParams) { w.WriteHeader(http.StatusNotImplemented) } // check if object exists -// (HEAD /object/{user}/{repository}) -func (_ Unimplemented) HeadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params HeadObjectParams) { +// (HEAD /object/{owner}/{repository}) +func (_ Unimplemented) HeadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params HeadObjectParams) { w.WriteHeader(http.StatusNotImplemented) } -// (POST /object/{user}/{repository}) -func (_ Unimplemented) UploadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params UploadObjectParams) { +// (POST /object/{owner}/{repository}) +func (_ Unimplemented) UploadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params UploadObjectParams) { w.WriteHeader(http.StatusNotImplemented) } // delete repository -// (DELETE /repos/{user}/{repository}) -func (_ Unimplemented) DeleteRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string) { +// (DELETE /repos/{owner}/{repository}) +func (_ Unimplemented) DeleteRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) { w.WriteHeader(http.StatusNotImplemented) } // get repository -// (GET /repos/{user}/{repository}) -func (_ Unimplemented) GetRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string) { +// (GET /repos/{owner}/{repository}) +func (_ Unimplemented) GetRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) { w.WriteHeader(http.StatusNotImplemented) } // update repository -// (POST /repos/{user}/{repository}) -func (_ Unimplemented) UpdateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateRepositoryJSONRequestBody, user string, repository string) { +// (POST /repos/{owner}/{repository}) +func (_ Unimplemented) UpdateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateRepositoryJSONRequestBody, owner string, repository string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// delete branch +// (DELETE /repos/{owner}/{repository}/branch) +func (_ Unimplemented) DeleteBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteBranchParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get branch +// (GET /repos/{owner}/{repository}/branch) +func (_ Unimplemented) GetBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetBranchParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// create branch +// (POST /repos/{owner}/{repository}/branch) +func (_ Unimplemented) CreateBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateBranchJSONRequestBody, owner string, repository string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// list branches +// (GET /repos/{owner}/{repository}/branches) +func (_ Unimplemented) ListBranches(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) { w.WriteHeader(http.StatusNotImplemented) } // get commits in repository -// (GET /repos/{user}/{repository}/commits) -func (_ Unimplemented) GetCommitsInRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params GetCommitsInRepositoryParams) { +// (GET /repos/{owner}/{repository}/commits) +func (_ Unimplemented) GetCommitsInRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetCommitsInRepositoryParams) { w.WriteHeader(http.StatusNotImplemented) } // get commit differences -// (GET /repos/{user}/{repository}/compare/{basehead}) -func (_ Unimplemented) GetCommitDiff(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, basehead string, params GetCommitDiffParams) { +// (GET /repos/{owner}/{repository}/compare/{basehead}) +func (_ Unimplemented) GetCommitDiff(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, basehead string, params GetCommitDiffParams) { w.WriteHeader(http.StatusNotImplemented) } -// list entries in commit -// (GET /repos/{user}/{repository}/contents/) -func (_ Unimplemented) GetEntriesInCommit(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, params GetEntriesInCommitParams) { +// list entries in ref +// (GET /repos/{owner}/{repository}/contents/) +func (_ Unimplemented) GetEntriesInRef(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetEntriesInRefParams) { w.WriteHeader(http.StatusNotImplemented) } @@ -4275,7 +4502,7 @@ func (_ Unimplemented) Register(ctx context.Context, w *JiaozifsResponse, r *htt // list repository // (GET /users/repos) -func (_ Unimplemented) ListRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request) { +func (_ Unimplemented) ListRepositoryOfAuthenticatedUser(ctx context.Context, w *JiaozifsResponse, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } @@ -4291,6 +4518,12 @@ func (_ Unimplemented) GetUserInfo(ctx context.Context, w *JiaozifsResponse, r * w.WriteHeader(http.StatusNotImplemented) } +// list repository in specific owner +// (GET /users/{owner}/repos) +func (_ Unimplemented) ListRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string) { + w.WriteHeader(http.StatusNotImplemented) +} + // return program and runtime version // (GET /version) func (_ Unimplemented) GetVersion(ctx context.Context, w *JiaozifsResponse, r *http.Request) { @@ -4298,62 +4531,38 @@ func (_ Unimplemented) GetVersion(ctx context.Context, w *JiaozifsResponse, r *h } // remove working in process -// (DELETE /wip/{repository}) -func (_ Unimplemented) DeleteWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string, params DeleteWipParams) { +// (DELETE /wip/{owner}/{repository}) +func (_ Unimplemented) DeleteWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteWipParams) { w.WriteHeader(http.StatusNotImplemented) } // get working in process -// (GET /wip/{repository}) -func (_ Unimplemented) GetWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string, params GetWipParams) { +// (GET /wip/{owner}/{repository}) +func (_ Unimplemented) GetWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetWipParams) { w.WriteHeader(http.StatusNotImplemented) } // create working in process -// (POST /wip/{repository}) -func (_ Unimplemented) CreateWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string, params CreateWipParams) { +// (POST /wip/{owner}/{repository}) +func (_ Unimplemented) CreateWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params CreateWipParams) { w.WriteHeader(http.StatusNotImplemented) } // get working in process changes -// (GET /wip/{repository}/changes) -func (_ Unimplemented) GetWipChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string, params GetWipChangesParams) { +// (GET /wip/{owner}/{repository}/changes) +func (_ Unimplemented) GetWipChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetWipChangesParams) { w.WriteHeader(http.StatusNotImplemented) } // commit working in process to branch -// (POST /wip/{repository}/commit) -func (_ Unimplemented) CommitWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string, params CommitWipParams) { +// (POST /wip/{owner}/{repository}/commit) +func (_ Unimplemented) CommitWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params CommitWipParams) { w.WriteHeader(http.StatusNotImplemented) } // list wip in specific project and user -// (GET /wip/{repository}/list) -func (_ Unimplemented) ListWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, repository string) { - w.WriteHeader(http.StatusNotImplemented) -} - -// list branches -// (GET /{user}/{reopsitory}/branches) -func (_ Unimplemented) ListBranches(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, reopsitory string) { - w.WriteHeader(http.StatusNotImplemented) -} - -// create branch -// (POST /{user}/{reopsitory}/branches) -func (_ Unimplemented) CreateBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateBranchJSONRequestBody, user string, reopsitory string) { - w.WriteHeader(http.StatusNotImplemented) -} - -// delete branch -// (DELETE /{user}/{repository}/branches/{branch}) -func (_ Unimplemented) DeleteBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, branch string) { - w.WriteHeader(http.StatusNotImplemented) -} - -// get branch -// (GET /{user}/{repository}/branches/{branch}) -func (_ Unimplemented) GetBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, user string, repository string, branch string) { +// (GET /wip/{owner}/{repository}/list) +func (_ Unimplemented) ListWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) { w.WriteHeader(http.StatusNotImplemented) } @@ -4418,12 +4627,12 @@ func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.R var err error - // ------------- Path parameter "user" ------------- - var user string + // ------------- Path parameter "owner" ------------- + var owner string - err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) return } @@ -4491,7 +4700,7 @@ func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.R } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.DeleteObject(r.Context(), &JiaozifsResponse{w}, r, user, repository, params) + siw.Handler.DeleteObject(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -4507,12 +4716,12 @@ func (siw *ServerInterfaceWrapper) GetObject(w http.ResponseWriter, r *http.Requ var err error - // ------------- Path parameter "user" ------------- - var user string + // ------------- Path parameter "owner" ------------- + var owner string - err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) return } @@ -4586,7 +4795,7 @@ func (siw *ServerInterfaceWrapper) GetObject(w http.ResponseWriter, r *http.Requ } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetObject(r.Context(), &JiaozifsResponse{w}, r, user, repository, params) + siw.Handler.GetObject(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -4602,12 +4811,12 @@ func (siw *ServerInterfaceWrapper) HeadObject(w http.ResponseWriter, r *http.Req var err error - // ------------- Path parameter "user" ------------- - var user string + // ------------- Path parameter "owner" ------------- + var owner string - err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) return } @@ -4681,7 +4890,7 @@ func (siw *ServerInterfaceWrapper) HeadObject(w http.ResponseWriter, r *http.Req } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.HeadObject(r.Context(), &JiaozifsResponse{w}, r, user, repository, params) + siw.Handler.HeadObject(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -4697,12 +4906,12 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R var err error - // ------------- Path parameter "user" ------------- - var user string + // ------------- Path parameter "owner" ------------- + var owner string - err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) return } @@ -4791,7 +5000,7 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.UploadObject(r.Context(), &JiaozifsResponse{w}, r, user, repository, params) + siw.Handler.UploadObject(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -4807,12 +5016,204 @@ func (siw *ServerInterfaceWrapper) DeleteRepository(w http.ResponseWriter, r *ht var err error - // ------------- Path parameter "user" ------------- - var user string + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteRepository(r.Context(), &JiaozifsResponse{w}, r, owner, repository) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// GetRepository operation middleware +func (siw *ServerInterfaceWrapper) GetRepository(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetRepository(r.Context(), &JiaozifsResponse{w}, r, owner, repository) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// UpdateRepository operation middleware +func (siw *ServerInterfaceWrapper) UpdateRepository(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Body parse ------------- + var body UpdateRepositoryJSONRequestBody + parseBody := true + if parseBody { + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "Error unmarshalling body 'UpdateRepository' as JSON", http.StatusBadRequest) + return + } + } + + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UpdateRepository(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// DeleteBranch operation middleware +func (siw *ServerInterfaceWrapper) DeleteBranch(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params DeleteBranchParams + + // ------------- Required query parameter "refName" ------------- + + if paramValue := r.URL.Query().Get("refName"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteBranch(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// GetBranch operation middleware +func (siw *ServerInterfaceWrapper) GetBranch(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "owner" ------------- + var owner string - err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) return } @@ -4821,18 +5222,36 @@ func (siw *ServerInterfaceWrapper) DeleteRepository(w http.ResponseWriter, r *ht err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetBranchParams + + // ------------- Required query parameter "refName" ------------- + + if paramValue := r.URL.Query().Get("refName"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) return } - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - - ctx = context.WithValue(ctx, Basic_authScopes, []string{}) - - ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.DeleteRepository(r.Context(), &JiaozifsResponse{w}, r, user, repository) + siw.Handler.GetBranch(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -4842,18 +5261,28 @@ func (siw *ServerInterfaceWrapper) DeleteRepository(w http.ResponseWriter, r *ht handler.ServeHTTP(w, r.WithContext(ctx)) } -// GetRepository operation middleware -func (siw *ServerInterfaceWrapper) GetRepository(w http.ResponseWriter, r *http.Request) { +// CreateBranch operation middleware +func (siw *ServerInterfaceWrapper) CreateBranch(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error - // ------------- Path parameter "user" ------------- - var user string + // ------------- Body parse ------------- + var body CreateBranchJSONRequestBody + parseBody := true + if parseBody { + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "Error unmarshalling body 'CreateBranch' as JSON", http.StatusBadRequest) + return + } + } + + // ------------- Path parameter "owner" ------------- + var owner string - err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) return } @@ -4873,7 +5302,7 @@ func (siw *ServerInterfaceWrapper) GetRepository(w http.ResponseWriter, r *http. ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetRepository(r.Context(), &JiaozifsResponse{w}, r, user, repository) + siw.Handler.CreateBranch(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository) })) for _, middleware := range siw.HandlerMiddlewares { @@ -4883,28 +5312,18 @@ func (siw *ServerInterfaceWrapper) GetRepository(w http.ResponseWriter, r *http. handler.ServeHTTP(w, r.WithContext(ctx)) } -// UpdateRepository operation middleware -func (siw *ServerInterfaceWrapper) UpdateRepository(w http.ResponseWriter, r *http.Request) { +// ListBranches operation middleware +func (siw *ServerInterfaceWrapper) ListBranches(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error - // ------------- Body parse ------------- - var body UpdateRepositoryJSONRequestBody - parseBody := true - if parseBody { - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - http.Error(w, "Error unmarshalling body 'UpdateRepository' as JSON", http.StatusBadRequest) - return - } - } - - // ------------- Path parameter "user" ------------- - var user string + // ------------- Path parameter "owner" ------------- + var owner string - err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) return } @@ -4924,7 +5343,7 @@ func (siw *ServerInterfaceWrapper) UpdateRepository(w http.ResponseWriter, r *ht ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.UpdateRepository(r.Context(), &JiaozifsResponse{w}, r, body, user, repository) + siw.Handler.ListBranches(r.Context(), &JiaozifsResponse{w}, r, owner, repository) })) for _, middleware := range siw.HandlerMiddlewares { @@ -4940,12 +5359,12 @@ func (siw *ServerInterfaceWrapper) GetCommitsInRepository(w http.ResponseWriter, var err error - // ------------- Path parameter "user" ------------- - var user string + // ------------- Path parameter "owner" ------------- + var owner string - err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) return } @@ -4976,7 +5395,7 @@ func (siw *ServerInterfaceWrapper) GetCommitsInRepository(w http.ResponseWriter, } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetCommitsInRepository(r.Context(), &JiaozifsResponse{w}, r, user, repository, params) + siw.Handler.GetCommitsInRepository(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -4992,12 +5411,12 @@ func (siw *ServerInterfaceWrapper) GetCommitDiff(w http.ResponseWriter, r *http. var err error - // ------------- Path parameter "user" ------------- - var user string + // ------------- Path parameter "owner" ------------- + var owner string - err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) return } @@ -5037,7 +5456,7 @@ func (siw *ServerInterfaceWrapper) GetCommitDiff(w http.ResponseWriter, r *http. } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetCommitDiff(r.Context(), &JiaozifsResponse{w}, r, user, repository, basehead, params) + siw.Handler.GetCommitDiff(r.Context(), &JiaozifsResponse{w}, r, owner, repository, basehead, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -5047,18 +5466,18 @@ func (siw *ServerInterfaceWrapper) GetCommitDiff(w http.ResponseWriter, r *http. handler.ServeHTTP(w, r.WithContext(ctx)) } -// GetEntriesInCommit operation middleware -func (siw *ServerInterfaceWrapper) GetEntriesInCommit(w http.ResponseWriter, r *http.Request) { +// GetEntriesInRef operation middleware +func (siw *ServerInterfaceWrapper) GetEntriesInRef(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error - // ------------- Path parameter "user" ------------- - var user string + // ------------- Path parameter "owner" ------------- + var owner string - err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) return } @@ -5078,7 +5497,7 @@ func (siw *ServerInterfaceWrapper) GetEntriesInCommit(w http.ResponseWriter, r * ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context - var params GetEntriesInCommitParams + var params GetEntriesInRefParams // ------------- Optional query parameter "path" ------------- @@ -5097,7 +5516,7 @@ func (siw *ServerInterfaceWrapper) GetEntriesInCommit(w http.ResponseWriter, r * } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetEntriesInCommit(r.Context(), &JiaozifsResponse{w}, r, user, repository, params) + siw.Handler.GetEntriesInRef(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -5147,8 +5566,8 @@ func (siw *ServerInterfaceWrapper) Register(w http.ResponseWriter, r *http.Reque handler.ServeHTTP(w, r.WithContext(ctx)) } -// ListRepository operation middleware -func (siw *ServerInterfaceWrapper) ListRepository(w http.ResponseWriter, r *http.Request) { +// ListRepositoryOfAuthenticatedUser operation middleware +func (siw *ServerInterfaceWrapper) ListRepositoryOfAuthenticatedUser(w http.ResponseWriter, r *http.Request) { ctx := r.Context() ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) @@ -5158,7 +5577,7 @@ func (siw *ServerInterfaceWrapper) ListRepository(w http.ResponseWriter, r *http ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.ListRepository(r.Context(), &JiaozifsResponse{w}, r) + siw.Handler.ListRepositoryOfAuthenticatedUser(r.Context(), &JiaozifsResponse{w}, r) })) for _, middleware := range siw.HandlerMiddlewares { @@ -5220,6 +5639,38 @@ func (siw *ServerInterfaceWrapper) GetUserInfo(w http.ResponseWriter, r *http.Re handler.ServeHTTP(w, r.WithContext(ctx)) } +// ListRepository operation middleware +func (siw *ServerInterfaceWrapper) ListRepository(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListRepository(r.Context(), &JiaozifsResponse{w}, r, owner) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // GetVersion operation middleware func (siw *ServerInterfaceWrapper) GetVersion(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -5241,6 +5692,15 @@ func (siw *ServerInterfaceWrapper) DeleteWip(w http.ResponseWriter, r *http.Requ var err error + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } + // ------------- Path parameter "repository" ------------- var repository string @@ -5275,7 +5735,7 @@ func (siw *ServerInterfaceWrapper) DeleteWip(w http.ResponseWriter, r *http.Requ } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.DeleteWip(r.Context(), &JiaozifsResponse{w}, r, repository, params) + siw.Handler.DeleteWip(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -5291,6 +5751,15 @@ func (siw *ServerInterfaceWrapper) GetWip(w http.ResponseWriter, r *http.Request var err error + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } + // ------------- Path parameter "repository" ------------- var repository string @@ -5325,7 +5794,7 @@ func (siw *ServerInterfaceWrapper) GetWip(w http.ResponseWriter, r *http.Request } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetWip(r.Context(), &JiaozifsResponse{w}, r, repository, params) + siw.Handler.GetWip(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -5341,6 +5810,15 @@ func (siw *ServerInterfaceWrapper) CreateWip(w http.ResponseWriter, r *http.Requ var err error + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } + // ------------- Path parameter "repository" ------------- var repository string @@ -5390,7 +5868,7 @@ func (siw *ServerInterfaceWrapper) CreateWip(w http.ResponseWriter, r *http.Requ } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CreateWip(r.Context(), &JiaozifsResponse{w}, r, repository, params) + siw.Handler.CreateWip(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -5406,6 +5884,15 @@ func (siw *ServerInterfaceWrapper) GetWipChanges(w http.ResponseWriter, r *http. var err error + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } + // ------------- Path parameter "repository" ------------- var repository string @@ -5448,7 +5935,7 @@ func (siw *ServerInterfaceWrapper) GetWipChanges(w http.ResponseWriter, r *http. } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetWipChanges(r.Context(), &JiaozifsResponse{w}, r, repository, params) + siw.Handler.GetWipChanges(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -5464,6 +5951,15 @@ func (siw *ServerInterfaceWrapper) CommitWip(w http.ResponseWriter, r *http.Requ var err error + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } + // ------------- Path parameter "repository" ------------- var repository string @@ -5506,7 +6002,7 @@ func (siw *ServerInterfaceWrapper) CommitWip(w http.ResponseWriter, r *http.Requ } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CommitWip(r.Context(), &JiaozifsResponse{w}, r, repository, params) + siw.Handler.CommitWip(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -5522,186 +6018,12 @@ func (siw *ServerInterfaceWrapper) ListWip(w http.ResponseWriter, r *http.Reques var err error - // ------------- Path parameter "repository" ------------- - var repository string - - err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) - return - } - - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - - ctx = context.WithValue(ctx, Basic_authScopes, []string{}) - - ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.ListWip(r.Context(), &JiaozifsResponse{w}, r, repository) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r.WithContext(ctx)) -} - -// ListBranches operation middleware -func (siw *ServerInterfaceWrapper) ListBranches(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - var err error - - // ------------- Path parameter "user" ------------- - var user string - - err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) - return - } - - // ------------- Path parameter "reopsitory" ------------- - var reopsitory string - - err = runtime.BindStyledParameterWithOptions("simple", "reopsitory", chi.URLParam(r, "reopsitory"), &reopsitory, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "reopsitory", Err: err}) - return - } - - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - - ctx = context.WithValue(ctx, Basic_authScopes, []string{}) - - ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.ListBranches(r.Context(), &JiaozifsResponse{w}, r, user, reopsitory) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r.WithContext(ctx)) -} - -// CreateBranch operation middleware -func (siw *ServerInterfaceWrapper) CreateBranch(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - var err error - - // ------------- Body parse ------------- - var body CreateBranchJSONRequestBody - parseBody := true - if parseBody { - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - http.Error(w, "Error unmarshalling body 'CreateBranch' as JSON", http.StatusBadRequest) - return - } - } - - // ------------- Path parameter "user" ------------- - var user string - - err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) - return - } - - // ------------- Path parameter "reopsitory" ------------- - var reopsitory string - - err = runtime.BindStyledParameterWithOptions("simple", "reopsitory", chi.URLParam(r, "reopsitory"), &reopsitory, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "reopsitory", Err: err}) - return - } - - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - - ctx = context.WithValue(ctx, Basic_authScopes, []string{}) - - ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CreateBranch(r.Context(), &JiaozifsResponse{w}, r, body, user, reopsitory) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r.WithContext(ctx)) -} - -// DeleteBranch operation middleware -func (siw *ServerInterfaceWrapper) DeleteBranch(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - var err error - - // ------------- Path parameter "user" ------------- - var user string - - err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) - return - } - - // ------------- Path parameter "repository" ------------- - var repository string - - err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) - return - } - - // ------------- Path parameter "branch" ------------- - var branch string - - err = runtime.BindStyledParameterWithOptions("simple", "branch", chi.URLParam(r, "branch"), &branch, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "branch", Err: err}) - return - } - - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - - ctx = context.WithValue(ctx, Basic_authScopes, []string{}) - - ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.DeleteBranch(r.Context(), &JiaozifsResponse{w}, r, user, repository, branch) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r.WithContext(ctx)) -} - -// GetBranch operation middleware -func (siw *ServerInterfaceWrapper) GetBranch(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - var err error - - // ------------- Path parameter "user" ------------- - var user string + // ------------- Path parameter "owner" ------------- + var owner string - err = runtime.BindStyledParameterWithOptions("simple", "user", chi.URLParam(r, "user"), &user, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) return } @@ -5714,15 +6036,6 @@ func (siw *ServerInterfaceWrapper) GetBranch(w http.ResponseWriter, r *http.Requ return } - // ------------- Path parameter "branch" ------------- - var branch string - - err = runtime.BindStyledParameterWithOptions("simple", "branch", chi.URLParam(r, "branch"), &branch, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "branch", Err: err}) - return - } - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) ctx = context.WithValue(ctx, Basic_authScopes, []string{}) @@ -5730,7 +6043,7 @@ func (siw *ServerInterfaceWrapper) GetBranch(w http.ResponseWriter, r *http.Requ ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetBranch(r.Context(), &JiaozifsResponse{w}, r, user, repository, branch) + siw.Handler.ListWip(r.Context(), &JiaozifsResponse{w}, r, owner, repository) })) for _, middleware := range siw.HandlerMiddlewares { @@ -5865,82 +6178,85 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Post(options.BaseURL+"/auth/logout", wrapper.Logout) }) r.Group(func(r chi.Router) { - r.Delete(options.BaseURL+"/object/{user}/{repository}", wrapper.DeleteObject) + r.Delete(options.BaseURL+"/object/{owner}/{repository}", wrapper.DeleteObject) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/object/{user}/{repository}", wrapper.GetObject) + r.Get(options.BaseURL+"/object/{owner}/{repository}", wrapper.GetObject) }) r.Group(func(r chi.Router) { - r.Head(options.BaseURL+"/object/{user}/{repository}", wrapper.HeadObject) + r.Head(options.BaseURL+"/object/{owner}/{repository}", wrapper.HeadObject) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/object/{user}/{repository}", wrapper.UploadObject) + r.Post(options.BaseURL+"/object/{owner}/{repository}", wrapper.UploadObject) }) r.Group(func(r chi.Router) { - r.Delete(options.BaseURL+"/repos/{user}/{repository}", wrapper.DeleteRepository) + r.Delete(options.BaseURL+"/repos/{owner}/{repository}", wrapper.DeleteRepository) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/repos/{user}/{repository}", wrapper.GetRepository) + r.Get(options.BaseURL+"/repos/{owner}/{repository}", wrapper.GetRepository) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/repos/{user}/{repository}", wrapper.UpdateRepository) + r.Post(options.BaseURL+"/repos/{owner}/{repository}", wrapper.UpdateRepository) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/repos/{user}/{repository}/commits", wrapper.GetCommitsInRepository) + r.Delete(options.BaseURL+"/repos/{owner}/{repository}/branch", wrapper.DeleteBranch) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/repos/{user}/{repository}/compare/{basehead}", wrapper.GetCommitDiff) + r.Get(options.BaseURL+"/repos/{owner}/{repository}/branch", wrapper.GetBranch) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/repos/{user}/{repository}/contents/", wrapper.GetEntriesInCommit) + r.Post(options.BaseURL+"/repos/{owner}/{repository}/branch", wrapper.CreateBranch) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/setup", wrapper.GetSetupState) + r.Get(options.BaseURL+"/repos/{owner}/{repository}/branches", wrapper.ListBranches) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/users/register", wrapper.Register) + r.Get(options.BaseURL+"/repos/{owner}/{repository}/commits", wrapper.GetCommitsInRepository) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/users/repos", wrapper.ListRepository) + r.Get(options.BaseURL+"/repos/{owner}/{repository}/compare/{basehead}", wrapper.GetCommitDiff) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/users/repos", wrapper.CreateRepository) + r.Get(options.BaseURL+"/repos/{owner}/{repository}/contents/", wrapper.GetEntriesInRef) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/users/user", wrapper.GetUserInfo) + r.Get(options.BaseURL+"/setup", wrapper.GetSetupState) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/version", wrapper.GetVersion) + r.Post(options.BaseURL+"/users/register", wrapper.Register) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/users/repos", wrapper.ListRepositoryOfAuthenticatedUser) }) r.Group(func(r chi.Router) { - r.Delete(options.BaseURL+"/wip/{repository}", wrapper.DeleteWip) + r.Post(options.BaseURL+"/users/repos", wrapper.CreateRepository) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/wip/{repository}", wrapper.GetWip) + r.Get(options.BaseURL+"/users/user", wrapper.GetUserInfo) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/wip/{repository}", wrapper.CreateWip) + r.Get(options.BaseURL+"/users/{owner}/repos", wrapper.ListRepository) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/wip/{repository}/changes", wrapper.GetWipChanges) + r.Get(options.BaseURL+"/version", wrapper.GetVersion) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/wip/{repository}/commit", wrapper.CommitWip) + r.Delete(options.BaseURL+"/wip/{owner}/{repository}", wrapper.DeleteWip) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/wip/{repository}/list", wrapper.ListWip) + r.Get(options.BaseURL+"/wip/{owner}/{repository}", wrapper.GetWip) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/{user}/{reopsitory}/branches", wrapper.ListBranches) + r.Post(options.BaseURL+"/wip/{owner}/{repository}", wrapper.CreateWip) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/{user}/{reopsitory}/branches", wrapper.CreateBranch) + r.Get(options.BaseURL+"/wip/{owner}/{repository}/changes", wrapper.GetWipChanges) }) r.Group(func(r chi.Router) { - r.Delete(options.BaseURL+"/{user}/{repository}/branches/{branch}", wrapper.DeleteBranch) + r.Post(options.BaseURL+"/wip/{owner}/{repository}/commit", wrapper.CommitWip) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/{user}/{repository}/branches/{branch}", wrapper.GetBranch) + r.Get(options.BaseURL+"/wip/{owner}/{repository}/list", wrapper.ListWip) }) return r @@ -5949,70 +6265,71 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "cUSGNztOHT2XLphNvb7/bwAAAP//vlkOhcNeAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index cd3020bb..1db46220 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -62,6 +62,8 @@ components: type: apiKey in: cookie name: internal_auth_session + + schemas: LoginConfig: type: object @@ -98,7 +100,6 @@ components: - login_url - login_cookie_names - logout_url - SetupState: type: object properties: @@ -123,13 +124,35 @@ components: Ref: type: object required: + - ID + - RepositoryID - CommitHash - Name + - CreatorID + - CreatedAt + - UpdatedAt properties: + ID : + type: string + format: uuid + RepositoryID: + type: string + format: uuid CommitHash: type: string - Name: + Name : + type: string + Description : + type: string + CreatorID: + type: string + format: uuid + CreatedAt: type: string + format: date-time + UpdatedAt: + type: string + format: date-time RefList: type: object required: @@ -215,7 +238,6 @@ components: UpdatedAt: type: string format: date-time - Signature: type: object required: @@ -231,7 +253,6 @@ components: When: type: string format: date-time - Commit: type: object required: @@ -362,7 +383,6 @@ components: password: type: string minLength: 8 - UserInfo: type: object required: @@ -392,7 +412,6 @@ components: updateAt: type: string format: date-time - UserRegisterInfo: type: object required: @@ -408,7 +427,6 @@ components: email: type: string format: email - AuthenticationToken: type: object required: @@ -421,7 +439,6 @@ components: type: integer format: int64 description: Unix Epoch in seconds - VersionResult: type: object required: @@ -548,10 +565,10 @@ paths: 503: description: service unavailable - /object/{user}/{repository}: + /object/{owner}/{repository}: parameters: - in: path - name: user + name: owner required: true schema: type: string @@ -783,13 +800,18 @@ paths: 420: description: too many requests - /wip/{repository}: + /wip/{owner}/{repository}: parameters: - in: path name: repository required: true schema: type: string + - in: path + name: owner + required: true + schema: + type: string - in: query name: refName description: ref name @@ -856,8 +878,13 @@ paths: 502: description: internal server error - /wip/{repository}/changes: + /wip/{owner}/{repository}/changes: parameters: + - in: path + name: owner + required: true + schema: + type: string - in: path name: repository required: true @@ -895,8 +922,13 @@ paths: 403: description: Forbidden - /wip/{repository}/commit: + /wip/{owner}/{repository}/commit: parameters: + - in: path + name: owner + required: true + schema: + type: string - in: path name: repository required: true @@ -936,8 +968,13 @@ paths: 502: description: internal server error - /wip/{repository}/list: + /wip/{owner}/{repository}/list: parameters: + - in: path + name: owner + required: true + schema: + type: string - in: path name: repository required: true @@ -964,10 +1001,10 @@ paths: 403: description: Forbidden - /repos/{user}/{repository}/contents/: + /repos/{owner}/{repository}/contents/: parameters: - in: path - name: user + name: owner required: true schema: type: string @@ -979,8 +1016,8 @@ paths: get: tags: - commit - operationId: getEntriesInCommit - summary: list entries in commit + operationId: getEntriesInRef + summary: list entries in ref parameters: - in: query name: path @@ -990,7 +1027,7 @@ paths: type: string - in: query name: ref - description: specific ref + description: specific ref default to main branch required: false schema: type: string @@ -1012,10 +1049,10 @@ paths: 404: description: url not found - /repos/{user}/{repository}/compare/{basehead}: + /repos/{owner}/{repository}/compare/{basehead}: parameters: - in: path - name: user + name: owner required: true schema: type: string @@ -1057,10 +1094,10 @@ paths: 503: description: server internal error - /repos/{user}/{repository}/commits: + /repos/{owner}/{repository}/commits: parameters: - in: path - name: user + name: owner required: true schema: type: string @@ -1091,10 +1128,10 @@ paths: items: $ref: "#/components/schemas/Commit" - /repos/{user}/{repository}: + /repos/{owner}/{repository}: parameters: - in: path - name: user + name: owner required: true schema: type: string @@ -1156,12 +1193,39 @@ paths: 403: description: Forbidden -# 必须授权 - /users/repos: + /users/{owner}/repos: + parameters: + - in: path + name: owner + required: true + schema: + type: string get: tags: - repo operationId: listRepository + summary: list repository in specific owner + responses: + 200: + description: repository list + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Repository" + 400: + description: ValidationError + 401: + description: Unauthorized + 403: + description: Forbidden + + /users/repos: # 必须授权 + get: + tags: + - repo + operationId: listRepositoryOfAuthenticatedUser" summary: list repository responses: 200: @@ -1205,15 +1269,15 @@ paths: 403: description: Forbidden - /{user}/{reopsitory}/branches: + /repos/{owner}/{repository}/branches: parameters: - in: path - name: user + name: owner required: true schema: type: string - in: path - name: reopsitory + name: repository required: true schema: type: string @@ -1237,35 +1301,12 @@ paths: description: Too many requests default: description: Internal Server Error - post: - tags: - - branches - operationId: createBranch - summary: create branch - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/BranchCreation" - responses: - 201: - description: create branch success - 400: - description: ValidationError - 404: - description: Resource Not Found - 409: - description: Resource Conflicts With Target - 420: - description: Too many requests - default: - description: Internal Server Error - /{user}/{repository}/branches/{branch}: + + /repos/{owner}/{repository}/branch: parameters: - in: path - name: user + name: owner required: true schema: type: string @@ -1274,16 +1315,17 @@ paths: required: true schema: type: string - - in: path - name: branch - required: true - schema: - type: string get: tags: - branches operationId: getBranch summary: get branch + parameters: + - in: query + name: refName + required: true + schema: + type: string responses: 200: description: branch @@ -1304,6 +1346,12 @@ paths: - branches operationId: deleteBranch summary: delete branch + parameters: + - in: query + name: refName + required: true + schema: + type: string responses: 204: description: branch delete successfully @@ -1315,6 +1363,36 @@ paths: description: Too many requests default: description: Internal Server Error + post: + tags: + - branches + operationId: createBranch + summary: create branch + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/BranchCreation" + responses: + 201: + description: create branch success + content: + application/json: + schema: + $ref: "#/components/schemas/BranchCreation" + 400: + description: ValidationError + 404: + description: Resource Not Found + 409: + description: Resource Conflicts With Target + 420: + description: Too many requests + default: + description: Internal Server Error + + /auth/login: post: @@ -1408,5 +1486,7 @@ paths: $ref: "#/components/schemas/UserInfo" 401: description: Unauthorized + 403: + description: Forbiden default: description: Internal Server Error diff --git a/auth/auth_middleware.go b/auth/auth_middleware.go index 6a51128c..75075fa0 100644 --- a/auth/auth_middleware.go +++ b/auth/auth_middleware.go @@ -60,7 +60,7 @@ func Middleware(swagger *openapi3.T, authenticator Authenticator, secretStore cr return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // if request already authenticated - if _, userNotFoundErr := GetUser(r.Context()); userNotFoundErr == nil { + if _, userNotFoundErr := GetOperator(r.Context()); userNotFoundErr == nil { next.ServeHTTP(w, r) return } @@ -78,7 +78,7 @@ func Middleware(swagger *openapi3.T, authenticator Authenticator, secretStore cr return } if user != nil { - r = r.WithContext(WithUser(r.Context(), user)) + r = r.WithContext(WithOperator(r.Context(), user)) } next.ServeHTTP(w, r) }) diff --git a/auth/context.go b/auth/context.go index 2a1d829a..2ee8f147 100644 --- a/auth/context.go +++ b/auth/context.go @@ -13,7 +13,7 @@ const ( userContextKey contextKey = "user" ) -func GetUser(ctx context.Context) (*models.User, error) { +func GetOperator(ctx context.Context) (*models.User, error) { user, ok := ctx.Value(userContextKey).(*models.User) if !ok { return nil, fmt.Errorf("UserNotFound") @@ -21,6 +21,6 @@ func GetUser(ctx context.Context) (*models.User, error) { return user, nil } -func WithUser(ctx context.Context, user *models.User) context.Context { +func WithOperator(ctx context.Context, user *models.User) context.Context { return context.WithValue(ctx, userContextKey, user) } diff --git a/cmd/init.go b/cmd/init.go index 5b9786b4..25fbe588 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -1,10 +1,10 @@ package cmd import ( + "fmt" "os" "github.com/jiaozifs/jiaozifs/config" - "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -16,23 +16,42 @@ var initCmd = &cobra.Command{ Long: `create default config file for jiaoozifs`, PreRunE: func(cmd *cobra.Command, args []string) error { //protect duplicate bind flag with daemon + err := viper.BindPFlag("blockstore.local.path", cmd.Flags().Lookup("bs_path")) + if err != nil { + return err + } + + err = viper.BindPFlag("database.debug", cmd.Flags().Lookup("db_debug")) + if err != nil { + return err + } + return viper.BindPFlag("database.connection", cmd.Flags().Lookup("db")) }, RunE: func(cmd *cobra.Command, args []string) error { - err := config.InitConfig() + err := config.InitConfig(cfgFile) if err != nil { return err } - //create a blockstore in home path for default usage - defaultBsPath, err := homedir.Expand(config.DefaultLocalBSPath) + + cfg, err := config.LoadConfig(cfgFile) if err != nil { return err } - return os.MkdirAll(defaultBsPath, 0755) + fmt.Println(cfg.API.Listen) + if cfg.Blockstore.Type == "local" { + _, err = os.Stat(cfg.Blockstore.Local.Path) + if os.IsNotExist(err) { + return os.MkdirAll(cfg.Blockstore.Local.Path, 0755) + } + } + return nil }, } func init() { rootCmd.AddCommand(initCmd) + initCmd.Flags().String("bs_path", config.DefaultLocalBSPath, "config blockstore path") + initCmd.Flags().Bool("db_debug", false, "enable database debug") initCmd.Flags().String("db", "", "pg connection string eg. postgres://user:pass@localhost:5432/jiaozifs?sslmode=disable") } diff --git a/cmd/root.go b/cmd/root.go index c8b1ed59..af58b1ba 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,6 +3,7 @@ package cmd import ( "os" + "github.com/jiaozifs/jiaozifs/config" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -25,7 +26,12 @@ func Execute() { } } +func RootCmd() *cobra.Command { + return rootCmd +} func init() { rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.jiaozifs/config.yaml)") - _ = viper.BindPFlag("config", rootCmd.Flags().Lookup("config")) + rootCmd.PersistentFlags().String("listen", config.DefaultLocalBSPath, "config blockstore path") + _ = viper.BindPFlag("api.listen", rootCmd.PersistentFlags().Lookup("listen")) + _ = viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config")) } diff --git a/config/config.go b/config/config.go index 742f342f..f8a8f192 100644 --- a/config/config.go +++ b/config/config.go @@ -52,32 +52,29 @@ type AuthConfig struct { } `mapstructure:"ui_config"` } -func InitConfig() error { - // Find home directory. - home, err := os.UserHomeDir() - cobra.CheckErr(err) - jiaoziHome := path.Join(home, ".jiaozifs") - defaultPath := path.Join(jiaoziHome, "config.toml") +func InitConfig(cfgPath string) error { // Search config in home directory with name ".jiaozifs" (without extension). - viper.AddConfigPath(path.Join(home, ".jiaozifs")) + viper.AddConfigPath(cfgPath) viper.SetConfigType("toml") viper.SetConfigName("config") if len(viper.ConfigFileUsed()) == 0 { data := make(map[string]interface{}) - err = ms.Decode(defaultCfg, &data) + err := ms.Decode(defaultCfg, &data) if err != nil { return err } for k, v := range data { viper.SetDefault(k, v) } - err = os.MkdirAll(jiaoziHome, 0755) + + basePath := path.Dir(cfgPath) + err = os.MkdirAll(basePath, 0755) if err != nil { return err } - return viper.WriteConfigAs(defaultPath) + return viper.WriteConfigAs(cfgPath) } - return fmt.Errorf("config already exit in %s", defaultPath) + return fmt.Errorf("config already exit in %s", cfgPath) } // LoadConfig reads in config file and ENV variables if set. diff --git a/controller/branch_ctl.go b/controller/branch_ctl.go index c66a5d2d..af82aa07 100644 --- a/controller/branch_ctl.go +++ b/controller/branch_ctl.go @@ -2,42 +2,48 @@ package controller import ( "context" + "errors" "net/http" "time" - logging "github.com/ipfs/go-log/v2" + "github.com/jiaozifs/jiaozifs/auth" + "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/utils" "go.uber.org/fx" ) -var branchLog = logging.Logger("branch_ctl") - type BranchController struct { fx.In Repo models.IRepo } -func (bct BranchController) ListBranches(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repoName string) { - // Get user - user, err := bct.Repo.UserRepo().Get(ctx, &models.GetUserParams{Name: utils.String(userName)}) +func (bct BranchController) ListBranches(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string) { + operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return } - // Get repo - repository, err := bct.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ - CreatorID: user.ID, - Name: utils.String(repoName), - }) + + owner, err := bct.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + repository, err := bct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) if err != nil { w.Error(err) return } - // List branches - branches, err := bct.Repo.RefRepo().List(ctx, repository.ID) + + if operator.Name != owner.Name { + w.Forbidden() + return + } + + branches, err := bct.Repo.RefRepo().List(ctx, models.NewListRefParams().SetRepositoryID(repository.ID)) if err != nil { w.Error(err) return @@ -53,34 +59,34 @@ func (bct BranchController) ListBranches(ctx context.Context, w *api.JiaozifsRes w.JSON(api.RefList{Results: refs}) } -func (bct BranchController) CreateBranch(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.CreateBranchJSONRequestBody, userName string, repoName string) { - // Decode request body - bc := api.BranchCreation{ - Name: body.Name, - Source: body.Source, +func (bct BranchController) CreateBranch(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.CreateBranchJSONRequestBody, ownerName string, repositoryName string) { + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return } - branchLog.Info(bc) - // Get user - user, err := bct.Repo.UserRepo().Get(ctx, &models.GetUserParams{Name: utils.String(userName)}) + + owner, err := bct.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) if err != nil { w.Error(err) return } + + if operator.Name != owner.Name { + w.Forbidden() + return + } + // Get repo - repository, err := bct.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ - CreatorID: user.ID, - Name: utils.String(repoName), - }) + repository, err := bct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) if err != nil { w.Error(err) return } + // Get source ref - params := models.NewGetRefParams() - params.SetName(bc.Source) - params.SetRepositoryID(repository.ID) - ref, err := bct.Repo.RefRepo().Get(ctx, params) - if err != nil { + ref, err := bct.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(body.Name).SetRepositoryID(repository.ID)) + if err != nil && !errors.Is(err, models.ErrNotFound) { w.Error(err) return } @@ -88,41 +94,56 @@ func (bct BranchController) CreateBranch(ctx context.Context, w *api.JiaozifsRes newRef := &models.Ref{ RepositoryID: repository.ID, CommitHash: ref.CommitHash, - Name: bc.Name, + Name: body.Name, Description: ref.Description, - CreatorID: user.ID, + CreatorID: operator.ID, CreatedAt: time.Now(), UpdatedAt: time.Now(), } - _, err = bct.Repo.RefRepo().Insert(ctx, newRef) + newRef, err = bct.Repo.RefRepo().Insert(ctx, newRef) if err != nil { w.Error(err) return } - w.String("Branch created successfully") + w.JSON(api.Ref{ + CommitHash: newRef.CommitHash.Hex(), + CreatedAt: newRef.CreatedAt, + CreatorID: newRef.CreatorID, + Description: newRef.Description, + ID: newRef.ID, + Name: newRef.Name, + RepositoryID: newRef.RepositoryID, + UpdatedAt: newRef.UpdatedAt, + }, http.StatusCreated) } -func (bct BranchController) DeleteBranch(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repoName string, branch string) { - // Get user - user, err := bct.Repo.UserRepo().Get(ctx, &models.GetUserParams{Name: utils.String(userName)}) +func (bct BranchController) DeleteBranch(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.DeleteBranchParams) { + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return + } + + owner, err := bct.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) if err != nil { w.Error(err) return } + + if operator.Name != owner.Name { + w.Forbidden() + return + } + // Get repo - repository, err := bct.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ - CreatorID: user.ID, - Name: utils.String(repoName), - }) + repository, err := bct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) if err != nil { w.Error(err) return } + // Delete branch - params := models.NewDeleteRefParams() - params.SetName(branch) - params.SetRepositoryID(repository.ID) - err = bct.Repo.RefRepo().Delete(ctx, params) + err = bct.Repo.RefRepo().Delete(ctx, models.NewDeleteRefParams().SetName(params.RefName).SetRepositoryID(repository.ID)) if err != nil { w.Error(err) return @@ -130,27 +151,33 @@ func (bct BranchController) DeleteBranch(ctx context.Context, w *api.JiaozifsRes w.OK() } -func (bct BranchController) GetBranch(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repoName string, branch string) { - // Get user - user, err := bct.Repo.UserRepo().Get(ctx, &models.GetUserParams{Name: utils.String(userName)}) +func (bct BranchController) GetBranch(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.GetBranchParams) { + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return + } + + owner, err := bct.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) if err != nil { w.Error(err) return } + + if operator.Name != owner.Name { + w.Forbidden() + return + } + // Get repo - repository, err := bct.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ - CreatorID: user.ID, - Name: utils.String(repoName), - }) + repository, err := bct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) if err != nil { w.Error(err) return } + // Get branch - params := models.NewGetRefParams() - params.SetName(branch) - params.SetRepositoryID(repository.ID) - ref, err := bct.Repo.RefRepo().Get(ctx, params) + ref, err := bct.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(params.RefName).SetRepositoryID(repository.ID)) if err != nil { w.Error(err) return diff --git a/controller/commit_ctl.go b/controller/commit_ctl.go index c764aad1..f9c6ef2b 100644 --- a/controller/commit_ctl.go +++ b/controller/commit_ctl.go @@ -6,6 +6,8 @@ import ( "net/http" "strings" + "github.com/jiaozifs/jiaozifs/auth" + "github.com/jiaozifs/jiaozifs/utils" "github.com/jiaozifs/jiaozifs/versionmgr" @@ -21,13 +23,36 @@ type CommitController struct { Repo models.IRepo } -func (commitCtl CommitController) GetEntriesInCommit(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, _ string, _ string, params api.GetEntriesInCommitParams) { +func (commitCtl CommitController) GetEntriesInRef(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.GetEntriesInRefParams) { + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return + } + + owner, err := commitCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + repository, err := commitCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) + if err != nil { + w.Error(err) + return + } + + if operator.Name == ownerName { //todo check permission + w.Forbidden() + return + } + refName := "main" if params.Path != nil { refName = *params.Ref } - ref, err := commitCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(refName)) + ref, err := commitCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetRepositoryID(repository.ID).SetName(refName)) if err != nil { w.Error(err) return @@ -57,7 +82,18 @@ func (commitCtl CommitController) GetEntriesInCommit(ctx context.Context, w *api w.JSON(treeEntry) } -func (commitCtl CommitController) GetCommitDiff(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, _ string, _ string, basehead string, params api.GetCommitDiffParams) { +func (commitCtl CommitController) GetCommitDiff(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, _ string, basehead string, params api.GetCommitDiffParams) { + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return + } + + if operator.Name == ownerName { //todo check permission + w.Forbidden() + return + } + baseHead := strings.Split(basehead, "...") if len(baseHead) != 2 { w.BadRequest("invalid basehead must be base...head") diff --git a/controller/object_ctl.go b/controller/object_ctl.go index dafba3f5..5c8d4320 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -13,6 +13,7 @@ import ( logging "github.com/ipfs/go-log/v2" "github.com/go-openapi/swag" + "github.com/jiaozifs/jiaozifs/auth" "github.com/jiaozifs/jiaozifs/models/filemode" "github.com/jiaozifs/jiaozifs/versionmgr" @@ -37,26 +38,31 @@ type ObjectController struct { Repo models.IRepo } -func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, userName string, repositoryName string, params api.DeleteObjectParams) { //nolint - user, err := oct.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) +func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, ownerName string, repositoryName string, params api.DeleteObjectParams) { //nolint + user, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return } - repository, err := oct.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ - CreatorID: user.ID, - Name: utils.String(repositoryName), - }) + owner, err := oct.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) if err != nil { w.Error(err) return } - ref, err := oct.Repo.RefRepo().Get(ctx, &models.GetRefParams{ - RepositoryID: repository.ID, - Name: utils.String(params.Branch), - }) + if user.Name != ownerName { //todo check permission + w.Forbidden() + return + } + + repository, err := oct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) + if err != nil { + w.Error(err) + return + } + + ref, err := oct.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetRepositoryID(repository.ID).SetName(params.Branch)) if err != nil { w.Error(err) return @@ -87,30 +93,36 @@ func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsRes w.OK() } -func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, userName string, repositoryName string, params api.GetObjectParams) { //nolint - user, err := oct.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) +func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, ownerName string, repositoryName string, params api.GetObjectParams) { //nolint + user, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return } - repository, err := oct.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ - CreatorID: user.ID, - Name: utils.String(repositoryName), - }) + owner, err := oct.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) if err != nil { w.Error(err) return } - ref, err := oct.Repo.RefRepo().Get(ctx, &models.GetRefParams{ - RepositoryID: repository.ID, - Name: utils.String(params.Branch), - }) + if user.Name != ownerName { //todo check permission + w.Forbidden() + return + } + + repository, err := oct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) if err != nil { w.Error(err) return } + + ref, err := oct.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetRepositoryID(repository.ID).SetName(params.Branch)) + if err != nil { + w.Error(err) + return + } + commit, err := oct.Repo.CommitRepo().Commit(ctx, ref.CommitHash) if err != nil { w.Error(err) @@ -162,7 +174,7 @@ func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsRespon _, err = io.Copy(w, reader) if err != nil { objLog.With( - "user", userName, + "user", ownerName, "repo", repositoryName, "path", params.Path). Debugf("GetObject copy content %v", err) @@ -170,26 +182,31 @@ func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsRespon } } -func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, userName string, repository string, params api.HeadObjectParams) { //nolint - user, err := oct.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) +func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, ownerName string, repositoryName string, params api.HeadObjectParams) { //nolint + user, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return } - repo, err := oct.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ - CreatorID: user.ID, - Name: utils.String(repository), - }) + owner, err := oct.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) if err != nil { w.Error(err) return } - ref, err := oct.Repo.RefRepo().Get(ctx, &models.GetRefParams{ - RepositoryID: repo.ID, - Name: utils.String(params.Branch), - }) + if user.Name != ownerName { //todo check permission + w.Forbidden() + return + } + + repository, err := oct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) + if err != nil { + w.Error(err) + return + } + + ref, err := oct.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetRepositoryID(repository.ID).SetName(params.Branch)) if err != nil { w.Error(err) return @@ -252,7 +269,7 @@ func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsRespo } } -func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, userName string, repository string, params api.UploadObjectParams) { //nolint +func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, ownerName string, repositoryName string, params api.UploadObjectParams) { //nolint // read request body parse multipart for "content" and upload the data contentType := r.Header.Get("Content-Type") mediaType, p, err := mime.ParseMediaType(contentType) @@ -298,15 +315,37 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes } defer reader.Close() //nolint + user, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return + } + + owner, err := oct.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + if user.Name != ownerName { //todo check permission + w.Forbidden() + return + } + + _, err = oct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) + if err != nil { + w.Error(err) + return + } + + stash, err := oct.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetID(params.WipID)) + if err != nil { + w.Error(err) + return + } + var response api.ObjectStats err = oct.Repo.Transaction(ctx, func(dRepo models.IRepo) error { - stash, err := dRepo.WipRepo().Get(ctx, &models.GetWipParams{ - ID: params.WipID, - }) - if err != nil { - return err - } - workingTree, err := versionmgr.NewWorkTree(ctx, dRepo.FileTreeRepo(), models.NewRootTreeEntry(stash.CurrentTree)) if err != nil { return err diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index 22c4116a..75ad9b31 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -8,6 +8,8 @@ import ( "regexp" "time" + "github.com/google/uuid" + openapi_types "github.com/oapi-codegen/runtime/types" "github.com/jiaozifs/jiaozifs/utils/hash" @@ -22,10 +24,12 @@ import ( "go.uber.org/fx" ) +const DefaultBranchName = "main" + var maxNameLength = 20 var alphanumeric = regexp.MustCompile("^[a-zA-Z0-9_]*$") -var RepoNameBlackList = []string{"repository"} +var RepoNameBlackList = []string{"repository", "repo", "user", "users"} func CheckRepositoryName(name string) error { for _, blackName := range RepoNameBlackList { @@ -49,16 +53,39 @@ type RepositoryController struct { Repo models.IRepo } -func (repositoryCtl RepositoryController) ListRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request) { - user, err := auth.GetUser(ctx) +func (repositoryCtl RepositoryController) ListRepositoryOfAuthenticatedUser(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request) { + operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return } - repositories, err := repositoryCtl.Repo.RepositoryRepo().List(ctx, &models.ListRepoParams{ - CreatorID: user.ID, - }) + repositories, err := repositoryCtl.Repo.RepositoryRepo().List(ctx, models.NewListRepoParams().SetOwnerID(operator.ID)) + if err != nil { + w.Error(err) + return + } + w.JSON(repositories) +} + +func (repositoryCtl RepositoryController) ListRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string) { + owner, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return + } + if owner.ID != operator.ID { //todo check public or private and allow access public repos + w.Forbidden() + return + } + + repositories, err := repositoryCtl.Repo.RepositoryRepo().List(ctx, models.NewListRepoParams().SetOwnerID(owner.ID)) if err != nil { w.Error(err) return @@ -73,39 +100,70 @@ func (repositoryCtl RepositoryController) CreateRepository(ctx context.Context, return } - user, err := auth.GetUser(ctx) + operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return } + //create default ref + var createdRepo *models.Repository + err = repositoryCtl.Repo.Transaction(ctx, func(repo models.IRepo) error { + repoID := uuid.New() + defaultRef := &models.Ref{ + RepositoryID: repoID, + CommitHash: hash.Hash{}, + Name: DefaultBranchName, + CreatorID: operator.ID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + defaultRef, err := repositoryCtl.Repo.RefRepo().Insert(ctx, defaultRef) + if err != nil { + return err + } + repository := &models.Repository{ + ID: repoID, + Name: body.Name, + Description: body.Description, + HEAD: defaultRef.Name, + OwnerID: operator.ID, + CreatorID: operator.ID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + createdRepo, err = repositoryCtl.Repo.RepositoryRepo().Insert(ctx, repository) + return err + }) - repository := &models.Repository{ - Name: body.Name, - Description: body.Description, - HEAD: "main", - CreatorID: user.ID, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - repository, err = repositoryCtl.Repo.RepositoryRepo().Insert(ctx, repository) if err != nil { w.Error(err) return } - w.JSON(repository) + + w.JSON(api.Repository{ + CreatedAt: createdRepo.CreatedAt, + CreatorID: createdRepo.CreatorID, + Description: createdRepo.Description, + Head: createdRepo.HEAD, + ID: createdRepo.ID, + Name: createdRepo.Name, + UpdatedAt: createdRepo.UpdatedAt, + }) } -func (repositoryCtl RepositoryController) DeleteRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repositoryName string) { - user, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) +func (repositoryCtl RepositoryController) DeleteRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string) { + operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return } - repo, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ - CreatorID: user.ID, - Name: utils.String(repositoryName), - }) + if operator.Name != ownerName { + w.Forbidden() + return + } + + repo, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(operator.ID)) if err != nil { w.Error(err) return @@ -119,16 +177,25 @@ func (repositoryCtl RepositoryController) DeleteRepository(ctx context.Context, w.OK() } -func (repositoryCtl RepositoryController) GetRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repositoryName string) { - user, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) +func (repositoryCtl RepositoryController) GetRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string) { + operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return } - repo, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ - CreatorID: user.ID, - Name: utils.String(repositoryName), - }) + + owner, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + if operator.Name != owner.Name { //todo check public or private / and permission + w.Forbidden() + return + } + + repo, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) if err != nil { w.Error(err) return @@ -137,17 +204,25 @@ func (repositoryCtl RepositoryController) GetRepository(ctx context.Context, w * w.JSON(repo) } -func (repositoryCtl RepositoryController) UpdateRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.UpdateRepositoryJSONRequestBody, userName string, repositoryName string) { - user, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) +func (repositoryCtl RepositoryController) UpdateRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.UpdateRepositoryJSONRequestBody, ownerName string, repositoryName string) { + operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return } - repo, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ - CreatorID: user.ID, - Name: utils.String(repositoryName), - }) + owner, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + if operator.Name != ownerName { //todo check permission to modify owner repo + w.Forbidden() + return + } + + repo, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) if err != nil { w.Error(err) return @@ -160,17 +235,25 @@ func (repositoryCtl RepositoryController) UpdateRepository(ctx context.Context, } } -func (repositoryCtl RepositoryController) GetCommitsInRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string, repositoryName string, params api.GetCommitsInRepositoryParams) { - user, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) +func (repositoryCtl RepositoryController) GetCommitsInRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.GetCommitsInRepositoryParams) { + user, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return } - repo, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ - CreatorID: user.ID, - Name: utils.String(repositoryName), - }) + owner, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + if user.Name != ownerName { //todo check public or private + w.Forbidden() + return + } + + repo, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) if err != nil { w.Error(err) return @@ -180,7 +263,7 @@ func (repositoryCtl RepositoryController) GetCommitsInRepository(ctx context.Con if params.RefName != nil { refName = *params.RefName } - ref, err := repositoryCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(refName)) + ref, err := repositoryCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetRepositoryID(repo.ID).SetName(refName)) if err != nil { w.Error(err) return @@ -227,18 +310,3 @@ func (repositoryCtl RepositoryController) GetCommitsInRepository(ctx context.Con } w.JSON(commits) } - -func (repositoryCtl RepositoryController) ListRepositories(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, userName string) { - user, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(userName)) - if err != nil { - w.Error(err) - return - } - - repos, err := repositoryCtl.Repo.RepositoryRepo().List(ctx, models.NewListRepoParams().SetCreatorID(user.ID)) - if err != nil { - w.Error(err) - return - } - w.JSON(repos) -} diff --git a/controller/user_ctl.go b/controller/user_ctl.go index 961729e9..168443d3 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -74,9 +74,9 @@ func (userCtl UserController) Register(ctx context.Context, w *api.JiaozifsRespo func (userCtl UserController) GetUserInfo(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request) { // Get token from Header - user, err := auth.GetUser(ctx) + user, err := auth.GetOperator(ctx) if err != nil { - w.Error(err) + w.Code(http.StatusForbidden) return } diff --git a/controller/wip_ctl.go b/controller/wip_ctl.go index ab2cf0c0..21a65460 100644 --- a/controller/wip_ctl.go +++ b/controller/wip_ctl.go @@ -25,22 +25,29 @@ type WipController struct { Repo models.IRepo } -func (wipCtl WipController) CreateWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, repository string, params api.CreateWipParams) { - user, err := auth.GetUser(ctx) +// CreateWip create wip of branch +func (wipCtl WipController) CreateWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.CreateWipParams) { + operatorUser, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return } - ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(params.RefName)) + + owner, err := wipCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) if err != nil { w.Error(err) return } - repo, err := wipCtl.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{ - CreatorID: user.ID, - Name: utils.String(repository), - }) + repository, err := wipCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) + if err != nil { + w.Error(err) + return + } + + //todo check permission to operator ownerRepo + + ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetRepositoryID(repository.ID).SetName(params.RefName)) if err != nil { w.Error(err) return @@ -55,11 +62,11 @@ func (wipCtl WipController) CreateWip(ctx context.Context, w *api.JiaozifsRespon wip := &models.WorkingInProcess{ CurrentTree: baseCommit.TreeHash, BaseCommit: ref.CommitHash, - RepositoryID: repo.ID, + RepositoryID: repository.ID, RefID: ref.ID, State: 0, Name: params.Name, - CreatorID: user.ID, + CreatorID: operatorUser.ID, CreatedAt: time.Now(), UpdatedAt: time.Now(), } @@ -71,30 +78,33 @@ func (wipCtl WipController) CreateWip(ctx context.Context, w *api.JiaozifsRespon w.JSON(wip, http.StatusCreated) } -func (wipCtl WipController) GetWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, repositoryName string, params api.GetWipParams) { - user, err := auth.GetUser(ctx) +// GetWip get wip of specific repository, operator only get himself wip +func (wipCtl WipController) GetWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.GetWipParams) { + user, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return } - repository, err := wipCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName)) + owner, err := wipCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) if err != nil { w.Error(err) return } - ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(params.RefName)) + repository, err := wipCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) + if err != nil { + w.Error(err) + return + } + //todo check permission to operator ownerRepo + ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetRepositoryID(repository.ID).SetName(params.RefName)) if err != nil { w.Error(err) return } - wip, err := wipCtl.Repo.WipRepo().Get(ctx, &models.GetWipParams{ - RefID: ref.ID, - CreatorID: user.ID, - RepositoryID: repository.ID, - }) + wip, err := wipCtl.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetRefID(ref.ID).SetCreatorID(user.ID).SetRepositoryID(repository.ID)) if err != nil { w.Error(err) return @@ -103,14 +113,21 @@ func (wipCtl WipController) GetWip(ctx context.Context, w *api.JiaozifsResponse, w.JSON(wip) } -func (wipCtl WipController) ListWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, repositoryName string) { - user, err := auth.GetUser(ctx) +// ListWip return wips of branches, operator only see himself wips in specific repository +func (wipCtl WipController) ListWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string) { + user, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return + } + + owner, err := wipCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) if err != nil { w.Error(err) return } - repository, err := wipCtl.Repo.RepositoryRepo().Get(ctx, &models.GetRepoParams{Name: utils.String(repositoryName)}) + repository, err := wipCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) if err != nil { w.Error(err) return @@ -125,14 +142,21 @@ func (wipCtl WipController) ListWip(ctx context.Context, w *api.JiaozifsResponse w.JSON(wips) } -func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, repositoryName string, params api.CommitWipParams) { - user, err := auth.GetUser(ctx) +// CommitWip commit wip to branch, operator only could operator himself wip +func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName, repositoryName string, params api.CommitWipParams) { + operatorUser, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return } - repository, err := wipCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName)) + owner, err := wipCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + repository, err := wipCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) if err != nil { w.Error(err) return @@ -150,7 +174,7 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon return } - wip, err := wipCtl.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetRefID(ref.ID).SetCreatorID(user.ID).SetRepositoryID(repository.ID)) + wip, err := wipCtl.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetRefID(ref.ID).SetCreatorID(operatorUser.ID).SetRepositoryID(repository.ID)) if err != nil { w.Error(err) return @@ -168,7 +192,7 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon //add commit err = wipCtl.Repo.Transaction(ctx, func(repo models.IRepo) error { commitOp := versionmgr.NewCommitOp(repo, commit) - commit, err := commitOp.AddCommit(ctx, user, wip.ID, msg) + commit, err := commitOp.AddCommit(ctx, operatorUser, wip.ID, msg) if err != nil { return err } @@ -189,28 +213,34 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon w.JSON(wip) } -// DeleteWip delete a active working in process -func (wipCtl WipController) DeleteWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, repositoryName string, params api.DeleteWipParams) { - user, err := auth.GetUser(ctx) +// DeleteWip delete a active working in process operator only can delete himself wip +func (wipCtl WipController) DeleteWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.DeleteWipParams) { + operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return } - repository, err := wipCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName)) + owner, err := wipCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) if err != nil { w.Error(err) return } - ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(params.RefName)) + repository, err := wipCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) + if err != nil { + w.Error(err) + return + } + + ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetRepositoryID(repository.ID).SetName(params.RefName)) if err != nil { w.Error(err) return } deleteWipParams := models.NewDeleteWipParams(). - SetCreatorID(user.ID). + SetCreatorID(operator.ID). //todo admin delete SetRepositoryID(repository.ID). SetRefID(ref.ID) @@ -223,20 +253,27 @@ func (wipCtl WipController) DeleteWip(ctx context.Context, w *api.JiaozifsRespon w.OK() } -func (wipCtl WipController) GetWipChanges(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, repositoryName string, params api.GetWipChangesParams) { - user, err := auth.GetUser(ctx) +// GetWipChanges return wip difference, operator only see himself wip +func (wipCtl WipController) GetWipChanges(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName, repositoryName string, params api.GetWipChangesParams) { + user, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return } - repository, err := wipCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName)) + owner, err := wipCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) if err != nil { w.Error(err) return } - ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(params.RefName)) + repository, err := wipCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) + if err != nil { + w.Error(err) + return + } + + ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetRepositoryID(repository.ID).SetName(params.RefName)) if err != nil { w.Error(err) return diff --git a/go.mod b/go.mod index 2dd771d8..d1c3daf1 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,6 @@ require ( github.com/flowchartsman/swaggerui v0.0.0-20221017034628-909ed4f3701b github.com/getkin/kin-openapi v0.118.0 github.com/go-chi/chi/v5 v5.0.10 - github.com/go-git/go-billy/v5 v5.5.0 github.com/go-git/go-git/v5 v5.10.1 github.com/go-openapi/swag v0.22.4 github.com/go-test/deep v1.1.0 @@ -46,6 +45,7 @@ require ( github.com/prometheus/client_golang v1.17.0 github.com/puzpuzpuz/xsync v1.5.2 github.com/rs/cors v1.10.1 + github.com/smartystreets/goconvey v1.8.1 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.17.0 github.com/stretchr/testify v1.8.4 @@ -95,7 +95,6 @@ require ( github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/containerd/continuity v0.3.0 // indirect - github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/docker/cli v23.0.6+incompatible // indirect github.com/docker/docker v23.0.6+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect @@ -113,6 +112,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect + github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/securecookie v1.1.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect @@ -123,6 +123,7 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/jtolds/gls v4.20.0+incompatible // indirect github.com/klauspost/compress v1.17.0 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/kr/pretty v0.3.1 // indirect @@ -156,6 +157,7 @@ require ( github.com/sagikazarmark/locafero v0.3.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/smarty/assertions v1.15.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.10.0 // indirect github.com/spf13/cast v1.5.1 // indirect diff --git a/go.sum b/go.sum index 819577ac..1822d1a8 100644 --- a/go.sum +++ b/go.sum @@ -137,7 +137,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= -github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -177,7 +176,6 @@ github.com/getkin/kin-openapi v0.118.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BH github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= -github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git/v5 v5.10.1 h1:tu8/D8i+TWxgKpzQ3Vc43e+kkhXqtsZCKI/egajKnxk= github.com/go-git/go-git/v5 v5.10.1/go.mod h1:uEuHjxkHap8kAl//V5F/nNWwqIYtP/402ddd05mp0wg= @@ -279,6 +277,8 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= +github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= @@ -317,6 +317,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -378,7 +380,6 @@ github.com/oapi-codegen/nethttp-middleware v1.0.1 h1:ZWvwfnMU0eloHX1VEJmQscQm374 github.com/oapi-codegen/nethttp-middleware v1.0.1/go.mod h1:P7xtAvpoqNB+5obR9qRCeefH7YlXWSK3KgPs/9WB8tE= github.com/oapi-codegen/runtime v1.1.0 h1:rJpoNUawn5XTvekgfkvSZr0RqEnoYpFkyvrzfWeFKWM= github.com/oapi-codegen/runtime v1.1.0/go.mod h1:BeSfBkWWWnAnGdyS+S/GnlbmHKzf8/hwkvelJZDeKA8= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= @@ -431,6 +432,10 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= +github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= +github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= +github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= diff --git a/integrationtest/helper.go b/integrationtest/helper.go new file mode 100644 index 00000000..b9eedd26 --- /dev/null +++ b/integrationtest/helper.go @@ -0,0 +1,87 @@ +package integrationtest + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "os" + "strings" + "testing" + "time" + + "github.com/jiaozifs/jiaozifs/testhelper" + + "github.com/stretchr/testify/require" + + "github.com/jiaozifs/jiaozifs/cmd" + "github.com/phayes/freeport" +) + +func InitCmd(ctx context.Context, jzHome string, listen string, db string) error { + buf := new(bytes.Buffer) + cmd.RootCmd().SetOut(buf) + cmd.RootCmd().SetErr(buf) + cmd.RootCmd().SetArgs([]string{"init", "--listen", listen, "--db_debug", "true", "--db", db, + "--config", fmt.Sprintf("%s/config.toml", jzHome), "--bs_path", fmt.Sprintf("%s/blockstore", jzHome)}) + + return cmd.RootCmd().ExecuteContext(ctx) +} + +func Daemon(ctx context.Context, writer io.Writer, jzHome string, listen string) error { + cmd.RootCmd().SetOut(writer) + cmd.RootCmd().SetErr(writer) + cmd.RootCmd().SetArgs([]string{"daemon", "--listen", listen, "--config", fmt.Sprintf("%s/config.toml", jzHome)}) + return cmd.RootCmd().ExecuteContext(ctx) +} + +type Closer func() + +func SetupDaemon(t *testing.T, ctx context.Context) (string, Closer) { //nolint + pg, connectString, _ := testhelper.SetupDatabase(ctx, t) + + port, err := freeport.GetFreePort() + require.NoError(t, err) + url := fmt.Sprintf("http://127.0.0.1:%d", port) + ctx, cancel := context.WithCancel(ctx) + tmpDir, err := os.MkdirTemp(os.TempDir(), "*") + require.NoError(t, err) + require.NoError(t, InitCmd(ctx, tmpDir, url, connectString)) + buf := new(bytes.Buffer) + + closer := func() { + cancel() + require.NoError(t, pg.Stop()) + } + go func() { + err := Daemon(ctx, buf, tmpDir, url) + if err != nil && !errors.Is(err, context.Canceled) { + require.NoError(t, err) + } + }() + fmt.Println(connectString) + + //wai for api ready + ticker := time.NewTicker(time.Second) + tryCount := 0 + for { + select { + case <-ticker.C: + readAll, err := io.ReadAll(buf) + require.NoError(t, err) + if strings.Contains(string(readAll), "") { + return url, closer + } + tryCount++ + if tryCount > 5 { + require.NoError(t, errors.New("timeout to wait api not ready")) + return "", nil + } + case <-ctx.Done(): + closer() + require.NoError(t, errors.New("context canceled")) + return "", nil + } + } +} diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go new file mode 100644 index 00000000..7d48ae28 --- /dev/null +++ b/integrationtest/repo_test.go @@ -0,0 +1,97 @@ +package integrationtest + +import ( + "context" + "fmt" + "net/http" + + apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" + + "github.com/jiaozifs/jiaozifs/controller" + + "github.com/jiaozifs/jiaozifs/utils" + + "github.com/jiaozifs/jiaozifs/api" + "github.com/smartystreets/goconvey/convey" +) + +func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { + client, _ := api.NewClient(urlStr + apiimpl.APIV1Prefix) + return func(c convey.C) { + userName := "jimmy" + c.Convey("create new user for repo", func() { + resp, err := client.Register(ctx, api.RegisterJSONRequestBody{ + Username: userName, + Password: "12345678", + Email: "mock@gmail.com", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(http.StatusOK, convey.ShouldEqual, resp.StatusCode) + + loginResp, err := client.Login(ctx, api.LoginJSONRequestBody{ + Username: userName, + Password: "12345678", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(http.StatusOK, convey.ShouldEqual, loginResp.StatusCode) + + client.RequestEditors = append(client.RequestEditors, func(ctx context.Context, req *http.Request) error { + for _, cookie := range loginResp.Cookies() { + req.AddCookie(cookie) + } + return nil + }) + }) + + c.Convey("forbidden create repo name", func() { + resp, err := client.CreateRepository(ctx, api.CreateRepository{ + Description: utils.String("test resp"), + Name: "repo", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(http.StatusBadRequest, convey.ShouldEqual, resp.StatusCode) + }) + + c.Convey("success create repo name", func() { + resp, err := client.CreateRepository(ctx, api.CreateRepository{ + Description: utils.String("test resp"), + Name: "happyrun", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(http.StatusOK, convey.ShouldEqual, resp.StatusCode) + + grp, err := api.ParseGetRepositoryResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(controller.DefaultBranchName, convey.ShouldEqual, grp.JSON200.Head) + fmt.Println(grp.JSON200.ID) + //check default branch created + branchResp, err := client.GetBranch(ctx, userName, grp.JSON200.Name, &api.GetBranchParams{RefName: controller.DefaultBranchName}) + convey.So(err, convey.ShouldBeNil) + convey.So(http.StatusOK, convey.ShouldEqual, branchResp.StatusCode) + + brp, err := api.ParseGetBranchResponse(branchResp) + convey.So(err, convey.ShouldBeNil) + convey.So(controller.DefaultBranchName, convey.ShouldEqual, brp.JSON200.Name) + }) + + c.Convey("duplicate repo", func() { + resp, err := client.CreateRepository(ctx, api.CreateRepository{ + Description: utils.String("test resp"), + Name: "happyrun", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(http.StatusInternalServerError, convey.ShouldEqual, resp.StatusCode) + }) + + c.Convey("list repository", func() { + resp, err := client.ListRepository(ctx, userName) + convey.So(err, convey.ShouldBeNil) + convey.So(http.StatusOK, convey.ShouldEqual, resp.StatusCode) + + listRepos, err := api.ParseListRepositoryResponse(resp) + convey.So(err, convey.ShouldBeNil) + + convey.So(len(*listRepos.JSON200), convey.ShouldEqual, 1) + }) + } +} diff --git a/integrationtest/root_test.go b/integrationtest/root_test.go new file mode 100644 index 00000000..dcea5fe1 --- /dev/null +++ b/integrationtest/root_test.go @@ -0,0 +1,17 @@ +package integrationtest + +import ( + "context" + "testing" + + "github.com/smartystreets/goconvey/convey" +) + +func TestSpec(t *testing.T) { + ctx := context.Background() + urlStr, cancel := SetupDaemon(t, ctx) + defer cancel() + + convey.Convey("user test", t, UserSpec(ctx, urlStr)) + convey.Convey("repo test", t, RepoSpec(ctx, urlStr)) +} diff --git a/integrationtest/simple_running_test.go b/integrationtest/simple_running_test.go new file mode 100644 index 00000000..811f42a3 --- /dev/null +++ b/integrationtest/simple_running_test.go @@ -0,0 +1,13 @@ +package integrationtest + +import ( + "context" + "testing" + "time" +) + +func TestRunning(t *testing.T) { + _, cancel := SetupDaemon(t, context.Background()) + time.Sleep(time.Second * 5) + cancel() +} diff --git a/integrationtest/user_test.go b/integrationtest/user_test.go new file mode 100644 index 00000000..94ad648f --- /dev/null +++ b/integrationtest/user_test.go @@ -0,0 +1,55 @@ +package integrationtest + +import ( + "context" + "net/http" + + apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" + + "github.com/jiaozifs/jiaozifs/api" + "github.com/smartystreets/goconvey/convey" +) + +func UserSpec(ctx context.Context, urlStr string) func(c convey.C) { + client, _ := api.NewClient(urlStr + apiimpl.APIV1Prefix) + return func(c convey.C) { + userName := "admin" + c.Convey("register", func() { + resp, err := client.Register(ctx, api.RegisterJSONRequestBody{ + Username: userName, + Password: "12345678", + Email: "mock@gmail.com", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(http.StatusOK, convey.ShouldEqual, resp.StatusCode) + }) + + c.Convey("usr profile no cookie", func() { + resp, err := client.GetUserInfo(ctx) + convey.So(err, convey.ShouldBeNil) + convey.So(http.StatusForbidden, convey.ShouldEqual, resp.StatusCode) + }) + + c.Convey("login", func() { + resp, err := client.Login(ctx, api.LoginJSONRequestBody{ + Username: "admin", + Password: "12345678", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(http.StatusOK, convey.ShouldEqual, resp.StatusCode) + + client.RequestEditors = append(client.RequestEditors, func(ctx context.Context, req *http.Request) error { + for _, cookie := range resp.Cookies() { + req.AddCookie(cookie) + } + return nil + }) + }) + + c.Convey("usr profile", func() { + resp, err := client.GetUserInfo(ctx) + convey.So(err, convey.ShouldBeNil) + convey.So(http.StatusOK, convey.ShouldEqual, resp.StatusCode) + }) + } +} diff --git a/models/merge_request.go b/models/merge_request.go index 487a7082..b8388d9e 100644 --- a/models/merge_request.go +++ b/models/merge_request.go @@ -75,5 +75,5 @@ func (m MergeRequestRepo) Get(ctx context.Context, params *GetMergeRequestParams query = query.Where("id = ?", params.ID) } - return mergeRequest, query.Limit(1).Scan(ctx, mergeRequest) + return mergeRequest, query.Limit(1).Scan(ctx) } diff --git a/models/ref.go b/models/ref.go index 545676e2..99aacd30 100644 --- a/models/ref.go +++ b/models/ref.go @@ -66,6 +66,10 @@ func (gup *DeleteRefParams) SetRepositoryID(repositoryID uuid.UUID) *DeleteRefPa gup.RepositoryID = repositoryID return gup } +func (gup *DeleteRefParams) SetID(id uuid.UUID) *DeleteRefParams { + gup.ID = id + return gup +} func (gup *DeleteRefParams) SetName(name string) *DeleteRefParams { gup.Name = &name @@ -87,12 +91,25 @@ func (up *UpdateRefParams) SetCommitHash(commitHash hash.Hash) *UpdateRefParams return up } +type ListRefParams struct { + RepositoryID uuid.UUID +} + +func NewListRefParams() *ListRefParams { + return &ListRefParams{} +} + +func (gup *ListRefParams) SetRepositoryID(repositoryID uuid.UUID) *ListRefParams { + gup.RepositoryID = repositoryID + return gup +} + type IRefRepo interface { Insert(ctx context.Context, repo *Ref) (*Ref, error) UpdateByID(ctx context.Context, params *UpdateRefParams) error Get(ctx context.Context, id *GetRefParams) (*Ref, error) - List(ctx context.Context, id uuid.UUID) ([]Ref, error) + List(ctx context.Context, params *ListRefParams) ([]*Ref, error) Delete(ctx context.Context, params *DeleteRefParams) error } @@ -130,27 +147,37 @@ func (r RefRepo) Get(ctx context.Context, params *GetRefParams) (*Ref, error) { query = query.Where("name = ?", *params.Name) } - return repo, query.Limit(1).Scan(ctx, repo) + return repo, query.Limit(1).Scan(ctx) } -func (r RefRepo) List(ctx context.Context, id uuid.UUID) ([]Ref, error) { - var refs []Ref - return refs, r.db.NewSelect().Model(&refs).Where("id = ?", id).Scan(ctx) +func (r RefRepo) List(ctx context.Context, params *ListRefParams) ([]*Ref, error) { + var refs []*Ref + query := r.db.NewSelect().Model(&refs) + + if uuid.Nil != params.RepositoryID { + query = query.Where("repository_id = ?", params.RepositoryID) + } + + return refs, query.Scan(ctx) } func (r RefRepo) Delete(ctx context.Context, params *DeleteRefParams) error { - ref := &Ref{} - query := r.db.NewSelect().Model(ref) + query := r.db.NewDelete().Model((*Ref)(nil)) if uuid.Nil != params.ID { query = query.Where("id = ?", params.ID) } - if uuid.Nil != params.RepositoryID && params.Name != nil { - query = query.Where("repository_id = ? AND name = ?", params.RepositoryID, *params.Name) + if uuid.Nil != params.RepositoryID { + query = query.Where("repository_id = ?", params.RepositoryID) + } + + if params.Name != nil { + query = query.Where("name = ?", *params.Name) } - return query.Limit(1).Scan(ctx, ref) + _, err := query.Exec(ctx) + return err } func (r RefRepo) UpdateByID(ctx context.Context, updateModel *UpdateRefParams) error { diff --git a/models/ref_test.go b/models/ref_test.go index afd5fdbc..b5255989 100644 --- a/models/ref_test.go +++ b/models/ref_test.go @@ -45,4 +45,15 @@ func TestRefRepoInsert(t *testing.T) { }) require.NoError(t, err) require.Equal(t, mockHash, refAfterUpdated.CommitHash) + + list, err := repo.List(ctx, models.NewListRefParams().SetRepositoryID(ref.RepositoryID)) + require.NoError(t, err) + require.Len(t, list, 1) + + err = repo.Delete(ctx, models.NewDeleteRefParams().SetID(list[0].ID).SetRepositoryID(list[0].RepositoryID).SetName(list[0].Name)) + require.NoError(t, err) + + list, err = repo.List(ctx, models.NewListRefParams().SetRepositoryID(ref.RepositoryID)) + require.NoError(t, err) + require.Len(t, list, 0) } diff --git a/models/repository.go b/models/repository.go index 0180a239..ee24b9ea 100644 --- a/models/repository.go +++ b/models/repository.go @@ -11,9 +11,10 @@ import ( type Repository struct { bun.BaseModel `bun:"table:repositories"` ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` - Name string `bun:"name,notnull"` - Description *string `bun:"description"` + Name string `bun:"name,unique,notnull"` + OwnerID uuid.UUID `bun:"owner_id,unique,type:uuid,notnull"` HEAD string `bun:"head,notnull"` + Description *string `bun:"description"` CreatorID uuid.UUID `bun:"creator_id,type:uuid,notnull"` CreatedAt time.Time `bun:"created_at"` @@ -23,6 +24,7 @@ type Repository struct { type GetRepoParams struct { ID uuid.UUID CreatorID uuid.UUID + OwnerID uuid.UUID Name *string } @@ -35,6 +37,11 @@ func (gup *GetRepoParams) SetID(id uuid.UUID) *GetRepoParams { return gup } +func (gup *GetRepoParams) SetOwnerID(id uuid.UUID) *GetRepoParams { + gup.OwnerID = id + return gup +} + func (gup *GetRepoParams) SetCreatorID(creatorID uuid.UUID) *GetRepoParams { gup.CreatorID = creatorID return gup @@ -48,6 +55,7 @@ func (gup *GetRepoParams) SetName(name string) *GetRepoParams { type ListRepoParams struct { ID uuid.UUID CreatorID uuid.UUID + OwnerID uuid.UUID } func NewListRepoParams() *ListRepoParams { @@ -58,7 +66,10 @@ func (lrp *ListRepoParams) SetID(id uuid.UUID) *ListRepoParams { lrp.ID = id return lrp } - +func (lrp *ListRepoParams) SetOwnerID(ownerID uuid.UUID) *ListRepoParams { + lrp.OwnerID = ownerID + return lrp +} func (lrp *ListRepoParams) SetCreatorID(creatorID uuid.UUID) *ListRepoParams { lrp.CreatorID = creatorID return lrp @@ -97,6 +108,7 @@ func (up *UpdateRepoParams) SetDescription(description string) *UpdateRepoParams type IRepositoryRepo interface { Insert(ctx context.Context, repo *Repository) (*Repository, error) Get(ctx context.Context, params *GetRepoParams) (*Repository, error) + List(ctx context.Context, params *ListRepoParams) ([]*Repository, error) Delete(ctx context.Context, params *DeleteRepoParams) error UpdateByID(ctx context.Context, updateModel *UpdateRepoParams) error @@ -132,22 +144,29 @@ func (r *RepositoryRepo) Get(ctx context.Context, params *GetRepoParams) (*Repos query = query.Where("creator_id = ?", params.CreatorID) } + if uuid.Nil != params.OwnerID { + query = query.Where("owner_id = ?", params.OwnerID) + } + if params.Name != nil { query = query.Where("name = ?", *params.Name) } - return repo, query.Limit(1).Scan(ctx, repo) + return repo, query.Limit(1).Scan(ctx) } func (r *RepositoryRepo) List(ctx context.Context, params *ListRepoParams) ([]*Repository, error) { repos := []*Repository{} - query := r.db.NewSelect().Model((*Repository)(nil)) + query := r.db.NewSelect().Model(&repos) if uuid.Nil != params.CreatorID { query = query.Where("creator_id = ?", params.CreatorID) } - return repos, query.Scan(ctx, &repos) + if uuid.Nil != params.OwnerID { + query = query.Where("owner_id = ?", params.OwnerID) + } + return repos, query.Scan(ctx) } func (r *RepositoryRepo) Delete(ctx context.Context, params *DeleteRepoParams) error { diff --git a/models/wip.go b/models/wip.go index 9bf19d38..4d75c1f3 100644 --- a/models/wip.go +++ b/models/wip.go @@ -196,7 +196,7 @@ func (s *WipRepo) Get(ctx context.Context, params *GetWipParams) (*WorkingInProc func (s *WipRepo) List(ctx context.Context, params *ListWipParams) ([]*WorkingInProcess, error) { var resp []*WorkingInProcess - query := s.db.NewSelect().Model((*WorkingInProcess)(nil)) + query := s.db.NewSelect().Model(&resp) if uuid.Nil != params.CreatorID { query = query.Where("creator_id = ?", params.CreatorID) @@ -210,7 +210,7 @@ func (s *WipRepo) List(ctx context.Context, params *ListWipParams) ([]*WorkingIn query = query.Where("ref_id = ?", params.RefID) } - return resp, query.Scan(ctx, &resp) + return resp, query.Scan(ctx) } // Delete remove wip in table by id From d6750e920e40d53bebf374e7a4389f8fed91dd0a Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Tue, 19 Dec 2023 10:53:16 +0800 Subject: [PATCH 087/210] feat: support like match in listreposotory --- api/jiaozifs.gen.go | 188 +++++++++++++++++++++-------------- api/swagger.yml | 6 ++ controller/repository_ctl.go | 8 +- integrationtest/repo_test.go | 53 ++++++++-- models/repo.go | 9 ++ models/repository.go | 27 ++++- models/repository_test.go | 35 ++++++- 7 files changed, 235 insertions(+), 91 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index c29e94c3..88a49af6 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -342,6 +342,11 @@ type GetEntriesInRefParams struct { Ref *string `form:"ref,omitempty" json:"ref,omitempty"` } +// ListRepositoryParams defines parameters for ListRepository. +type ListRepositoryParams struct { + RepoPrefix *string `form:"repoPrefix,omitempty" json:"repoPrefix,omitempty"` +} + // DeleteWipParams defines parameters for DeleteWip. type DeleteWipParams struct { // RefName ref name @@ -546,7 +551,7 @@ type ClientInterface interface { GetUserInfo(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) // ListRepository request - ListRepository(ctx context.Context, owner string, reqEditors ...RequestEditorFn) (*http.Response, error) + ListRepository(ctx context.Context, owner string, params *ListRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetVersion request GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -882,8 +887,8 @@ func (c *Client) GetUserInfo(ctx context.Context, reqEditors ...RequestEditorFn) return c.Client.Do(req) } -func (c *Client) ListRepository(ctx context.Context, owner string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewListRepositoryRequest(c.Server, owner) +func (c *Client) ListRepository(ctx context.Context, owner string, params *ListRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListRepositoryRequest(c.Server, owner, params) if err != nil { return nil, err } @@ -2123,7 +2128,7 @@ func NewGetUserInfoRequest(server string) (*http.Request, error) { } // NewListRepositoryRequest generates requests for ListRepository -func NewListRepositoryRequest(server string, owner string) (*http.Request, error) { +func NewListRepositoryRequest(server string, owner string, params *ListRepositoryParams) (*http.Request, error) { var err error var pathParam0 string @@ -2148,6 +2153,28 @@ func NewListRepositoryRequest(server string, owner string) (*http.Request, error return nil, err } + if params != nil { + queryValues := queryURL.Query() + + if params.RepoPrefix != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "repoPrefix", runtime.ParamLocationQuery, *params.RepoPrefix); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err @@ -2680,7 +2707,7 @@ type ClientWithResponsesInterface interface { GetUserInfoWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetUserInfoResponse, error) // ListRepositoryWithResponse request - ListRepositoryWithResponse(ctx context.Context, owner string, reqEditors ...RequestEditorFn) (*ListRepositoryResponse, error) + ListRepositoryWithResponse(ctx context.Context, owner string, params *ListRepositoryParams, reqEditors ...RequestEditorFn) (*ListRepositoryResponse, error) // GetVersionWithResponse request GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) @@ -3563,8 +3590,8 @@ func (c *ClientWithResponses) GetUserInfoWithResponse(ctx context.Context, reqEd } // ListRepositoryWithResponse request returning *ListRepositoryResponse -func (c *ClientWithResponses) ListRepositoryWithResponse(ctx context.Context, owner string, reqEditors ...RequestEditorFn) (*ListRepositoryResponse, error) { - rsp, err := c.ListRepository(ctx, owner, reqEditors...) +func (c *ClientWithResponses) ListRepositoryWithResponse(ctx context.Context, owner string, params *ListRepositoryParams, reqEditors ...RequestEditorFn) (*ListRepositoryResponse, error) { + rsp, err := c.ListRepository(ctx, owner, params, reqEditors...) if err != nil { return nil, err } @@ -4365,7 +4392,7 @@ type ServerInterface interface { GetUserInfo(ctx context.Context, w *JiaozifsResponse, r *http.Request) // list repository in specific owner // (GET /users/{owner}/repos) - ListRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string) + ListRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, params ListRepositoryParams) // return program and runtime version // (GET /version) GetVersion(ctx context.Context, w *JiaozifsResponse, r *http.Request) @@ -4520,7 +4547,7 @@ func (_ Unimplemented) GetUserInfo(ctx context.Context, w *JiaozifsResponse, r * // list repository in specific owner // (GET /users/{owner}/repos) -func (_ Unimplemented) ListRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string) { +func (_ Unimplemented) ListRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, params ListRepositoryParams) { w.WriteHeader(http.StatusNotImplemented) } @@ -5660,8 +5687,19 @@ func (siw *ServerInterfaceWrapper) ListRepository(w http.ResponseWriter, r *http ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context + var params ListRepositoryParams + + // ------------- Optional query parameter "repoPrefix" ------------- + + err = runtime.BindQueryParameter("form", true, false, "repoPrefix", r.URL.Query(), ¶ms.RepoPrefix) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repoPrefix", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.ListRepository(r.Context(), &JiaozifsResponse{w}, r, owner) + siw.Handler.ListRepository(r.Context(), &JiaozifsResponse{w}, r, owner, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -6265,71 +6303,71 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "cUSGNztOHT2XLphNvb7/bwAAAP//vlkOhcNeAAA=", + "H4sIAAAAAAAC/+RcaXPbNvr/Khj+O/Nvs7p8NLN1p9OJHafxrpN6bKd5EXk1EPlQQkICLABaVj3+7jsA", + "eBOkKFm+um8yDgkCz/l7DgC6dVwWRowClcI5uHWEO4cQ6z/fxHIOVBIXS8LoJfsGVD2OOIuASwJ6kEwf", + "eyBcTiI11DlwMPrX50ukXyI5xxK5LA48NAUUC/CQZAjnswPi8GcMQgqn58hlBM6BIyQndObc9cwKE7iJ", + "CMdm9upinyi5QccRc+eIUCTAZdRTU/mMh1g6Bw6h8vV+PjehEmbAnbu7nqNWJhw85+BLwstVNo5Nv4Ir", + "FQ2HHFN3fsQho6AsBYpD0NKoEi9YzF3bq8rSeoJsuI2EozmmM6gv/cZNSSqya2G25xxiAe+xmFspPcPS", + "/uKSNXxTYUFP0EvpsbLAwpBICwuxnDOu/vqOg+8cOP83zI1ymFjk8ILMKJYxh3wqCWt+pRQI3htZEpeH", + "JfQl0Qqocd8orw/AZ3CJZw0vhcAzaBA0ByrVvIZ7IiEU1pHJA8w5XmpNcGjW36fIW4+3ivr0xD3nUg3q", + "pSopCrrAcs5ggagKZ0VpF6mzGoYeeQ4RE0QyvqybyNuiw1u4/2h3wAqPepSNgFM2I/SIUZ/M6mufH745", + "qoOOeooWJAgQhxATioDiaQAeYhT99ukEER+NHbiRwCkOxs4AoUuFg4wGS7Rg/JsY0wWRc4QpSkdpTEQC", + "+DVxYTCmTs8BGoeKckHCKCA+AU89TMYXWMkl4eMgmGL32yRQPE0CPIWgTr1+rGA4CrALiubKdzEPBs7q", + "6WNumdwgMOZL9On8VC3CfB+4Qn4u1H9jAchnHOkprKuYyV3GvhGYKGwU9VXMW6TfZlFFSMZBxR6nt4Zj", + "meV8TALwJmHuu+UFkxdqGY+IKMDLhBku0GLOkPpePdGz/Yww8uMgQAKoBOqCCYNEIA7UAw7emBKK3l9+", + "OEWYeijES+QyKpUlYRQQ+k0HSZTLUk+LQpBz5mnbaJCaVSURJ2FBIZ00wGJpn6w+yYzQGWKxHKyEmZxG", + "q5ZLC9s89Xf914XEJl0pe6o7B/ebUB5jUbqSLlA5MS+qPJl5UQgewUgaEKxNEYLEHpZ4VdAxk30SwD+k", + "X6ivNQ5vL3vpOVFTzFYvJiHzoBQMYkLl3q51JkH+gsl0KY0c102cMrknJCUEJGI0fDcrsySng1sHex5R", + "ssHBWTnVbHDjfL4zPCO0IUWbYzEJGbco4CPcSBQpzyYC4WtMAgXkOddTxgLAVKsQ30wi4JPIChAf8A0J", + "cYBoHE6BI+YjoJITECgCrldQ0iCUhMpERzY9ULiRE+b7AmR9fp2CZ1DHQc19rYAFEE15sJktBxEH0gKh", + "HzNCr3EQg0A+i6mnzFDNmX7WTnPFFDIxV4SVU1Fm0mYW58qzqvoziUhj+rNBaqc/YfzkbdlJYuLZRq/K", + "QDpO87GpUsizn44z3TfhO3nrVFbtFYWckFoU0zop3Tn4p0RYkv2o5KNtKFrw5rIRZ4G97WtlRLVQXxFB", + "gZZ8ATs3zanpk1vee8Deg5jkViwssSJN5KbGdAEyjlTMt9S/LgvDScTBF5OQCKHoqOGc5DGohFyhmhqP", + "9HiEOaDkm4EV7tMEJa0L2uytWEKogJpSm2bwhBJJcED+0ik8ZXJSfHJlk2VdDlkxWxPDcYhJUNIT6Cfr", + "6PvzHOiGqk60fJysqWeyaVJVi8dU2vyoEdpPxFvCC28KCmou+2orGwvbvMa0zimAn1CfWaxyfVBwY67K", + "Z6XjE7rxhydn5QQuut63fQPdzSXAYgOi8q86UhRr/ayzhKq8aKe6PxuZMn7VoMxzmBEhm5S6htAiLMSC", + "cY3LIaGnQGcqVf/ndtkorGPj6A/ggjB6rgNbnR0ckcm1GVKHTB5TJXeUDrCqWIKQxSlqQxqnjzibcRw2", + "T19hPR9XpNrG9GcS1Vk9xALy7uPjJ49HxkUV+j2P5DELpiubxpskARWlqHAIbsyJXF6oaGl0MsWCuBMc", + "mxJWh1GN7upxPutcyshU77pLkA4neQdIRVMtF001pzjQoyYCRNm0cET+Dbrf83UhJ9nGxRQwB/4u5cz0", + "jnJy9NsqPYolkmBE2bC/Esz+Ir5A7y8vz9CbsxOn5wTEBSog3yhw3kTYnQPaHYycnqN7LHpicTAcLhaL", + "AdavB4zPhsm3Ynh6cnT88eK4vzsYDeYyDHRyS2QAxUXNepnXOTuD0WCkRrIIKI6Ic+Ds6UemQtd6GCpp", + "DXWqox2HmaxduY9OjU8858A0SB3jkyDkIfOWJvnSPRWDJlGQbBUNvwrj8yY3stUAOTpuBw9bcPDOfCYi", + "puSoZt0djdYivi3ts22S6RUrLdHYdUEIPw5Mz83pOXPAHnBN0AXI/pEx5tLCcIPDKGg07V/w1PVgZ3fv", + "x9c/ozMs578Mf0bvpYx+p8HS4piKrP3Rjq0FhXW/X6Wi6A8cEE9zc8w50xiwvzuyJNWMoRDTZb55p7n2", + "cRJsyqNPEgbQBfBr4CiZuwANzsGXq54j4jDEKjtzIuAKbhDOJCbxTCi9axC4Ut9mtsti2Wq86r3dCtr0", + "pL56njKzS8lwaRGT8YXhLVtQ4HfDW57FizuzbAAmHJQF91Y/N1067WQchyC10X6pErtg/BuhM0QoijhT", + "QnR6Bqb/jIEvc5RekEiXf7knq/KsVzD7FdHr7qqmyf268AzLyLDmoVyxwbKTTs2gvfqgd4xPiecBNSMs", + "S39k8h2LqbeOGZSUaohGhoUB+mBq1OT/wuw6USYRBxlzijBKV0SgTGRQMILkG+fqrufMwOIcv4HspuDD", + "pQTEMTU7IGn3UW9HpSilG8i/jPo7o929VPsG5nL1n+tt7KK6IyyVnTsHzn/MBN9/Px57r/rqn96v6Ncf", + "/vHDd52soA3VmStB9oXkgMMyyGbWNiUUcytu9uy2lS5VwvIj87CfpvzWpVr66sfJnnL+VT0InmIh+x+Y", + "ZzYEWwer4buj148lmQhzSXCAHlJC6ffn6YGIe5vSg0h9b7Rr2TUGj3AlGb25F3HoCzKj4OmNOZ9x3aJi", + "qT8WhHbK3Kxp2r5uR2RrhkyFLH6GXzujxoH6TE4y385rG7Ma3cBDWlUKpdAFlkT4RO+wbAqPM5B1A7MB", + "3jzpjJYR7z1g728FeQ3KIeZA1YvApi4ognTF9b8IJX9Ll25JfNNqR5+FAW6ymgoI6D1nRHxUtXcbEFS8", + "nBgj0zvViY/qzLg1K61p0TpPnlmvO1lZBFN94lDBjtmK9RuyaTPufmtxCLAk17B6tYTX7mtd9Rpqsk9R", + "wLrC8COWFlo2EQcXy/zzMjVJLy9YIhFHEeNSmENdY+fV2NFxPQjYAsWaQUU2pqmN6nHKZCkgj4Gg/5/Y", + "LVqCHIzp22zpHpJzIpCLIzwlAZHLPOmfQrow6K16P5YxV0oLAAsQybmxLEC9aopKJ37/I6PQ/4ClNiAr", + "9I3HrxoDUZdO0H2Sy54TxoEkKhgM1eh+ekCkqa1UoKFyuEfJHSNVRAWAfBKAPpFhVIQWc+LOURgLLVsl", + "HQ+N08nGzqB4FqeF2A5tp52ttZ2Kx6CaC5SwcPpo35Yr2PoWGzU7NiuUYx5UQ5MlZz7j+kyUPhP0Tp/R", + "WzNzrEWEnnPTv8646MONG8Qe9KfalpXP66aJhvINeybn5TDQse2kjxaa2r8QR7apvC7KsrUiSmEtlad6", + "2NpYWCmFrfhCYRWLK6hi4bkIs0KLRZLPPlFpCeiV/fXNNwvalF1b5q68K6C9d02XM1vPz8ZK6uTUDKUd", + "noZJUrgSpQ7T5NFmdpXcioOfHPJYy1hWN2qTTDcBmg37tPu2UsXcptE1Sns/9nLrbfmEmyw7T/VnHkB7", + "P/bx1bJNMPZtKJwI4uUqVEF3uzZfLnSb4w+HxUpy27BduUDXCbR3HnT1ymUSLYJEwykIrRcGulvs6KeW", + "oUeM+gFxpUCfiZyjS8wVTDyeoZckYbf1TtHHaNEKcadEJBinb3w8JBbpM8iNeIQC/frFgpIiH01zSb5Q", + "XFphT64+uNVsTr+BNGe7xAkt5Z+tTSUO/vdGTkOJZz+g5CBJe4x9uJja6Ux7coStfqzdWvWkcqsHsuQN", + "IvTFlyOrbSfCHIa3UyxgDti7W21Gb4nvr7IeEYFLfOIixUUPEV/3MbKnycZ8ev1HyZkx2d5UfWrbMvfL", + "O9iWsR7kKTFtu1D60VYoJbsA2a4ANORnBcKAA3VLmGhevpjdAMtkqQlv2UG0EYlhm18cGztW8Pq8PKPX", + "uDoHHyWxVZX4+oZ2ltM0gPzTO2F+PaKzHz56r6JbL7eepxRVrmX9Ej1Tu5MAGUdt/lK4r/SA6W1hFYt1", + "ZGeCNbXI3Edaa3u2EYuJCyim+U3ZtlOc2TZtmZ6K+hlNygp9m37Ik2sYzUc604saD9VlrN4Fad7OqWaV", + "6iND6Moy8hB7KNlQR/3Ctgp6HgdvlS5QkSH72dJUZRFrr/jy1Px3v3BqGjwlbOcx0PW81OBdBa8atJ5L", + "T7hKjC1Xb+ntPHhbvrbMA3R4HkDHFBbPRsVJ42VV29+4m/q3LQJlNxMfMP5ka1gEe5Gfo9en9XyDJmb4", + "/aVn8pB7N3QJNRv7CnNZcj04O90RsNkMvD7Rv7jA27AvTazXwcCu3f2InXHwyc3T56breVZuxoUm2zNB", + "T/07K2mhkCaUj9L70Olj4VJkk/v+kV13fDDvLV8OtR3lrlzRbEsZkqou/QRTD1kukNoSvgWJNjxi8ZlE", + "G56tWJDoae2RQ8iuAVnPtaVSUkS2bRI2s78V81DTW4zCQvKTn6joJMbV3rzVXs1GZWqtQd2tKb21HUBj", + "Uu1HMUnURhS9/1b0zuObMEp+reEJWik/2i6JdDr+bLLFDrbfhrJDV3d9W/dWPpPoKBm1ektl2xbbq18N", + "kPOn6qN3aZ93s7dEns8QOjPaNoHQ59Ccazb1/NdnX9xtgEeNBVpOHWJBst0SZj8FayMtFLMn88mGAJDQ", + "neaFOllNSED6J1kpLJ4kR7xPODA8WfxZsvpZjg6BIUh+vK2xlN1C/tmp7PxsFLGq3nxuiakuOVW2VKw1", + "I870uXxlcpWmwgsC2XIdeFv8NZUvV2qx4i+7mCelX2/5cqW83hizDVcKOwXG3qkXMfPzNPlPpRwMhwFz", + "cTBnQh7s7f+0szfEERle7zh19Fw5Yfbp1d1/AwAA//+gRq99EF8AAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 1db46220..1fa06eff 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -1205,6 +1205,12 @@ paths: - repo operationId: listRepository summary: list repository in specific owner + parameters: + - in: query + name: repoPrefix + required: false + schema: + type: string responses: 200: description: repository list diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index 75ad9b31..0da13697 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -68,7 +68,7 @@ func (repositoryCtl RepositoryController) ListRepositoryOfAuthenticatedUser(ctx w.JSON(repositories) } -func (repositoryCtl RepositoryController) ListRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string) { +func (repositoryCtl RepositoryController) ListRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, params api.ListRepositoryParams) { owner, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) if err != nil { w.Error(err) @@ -85,7 +85,11 @@ func (repositoryCtl RepositoryController) ListRepository(ctx context.Context, w return } - repositories, err := repositoryCtl.Repo.RepositoryRepo().List(ctx, models.NewListRepoParams().SetOwnerID(owner.ID)) + listParams := models.NewListRepoParams().SetOwnerID(owner.ID) + if params.RepoPrefix != nil && len(*params.RepoPrefix) > 0 { + listParams.SetName(*params.RepoPrefix, models.PrefixMatch) + } + repositories, err := repositoryCtl.Repo.RepositoryRepo().List(ctx, listParams) if err != nil { w.Error(err) return diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index 7d48ae28..c1e839d3 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -26,14 +26,14 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { Email: "mock@gmail.com", }) convey.So(err, convey.ShouldBeNil) - convey.So(http.StatusOK, convey.ShouldEqual, resp.StatusCode) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) loginResp, err := client.Login(ctx, api.LoginJSONRequestBody{ Username: userName, Password: "12345678", }) convey.So(err, convey.ShouldBeNil) - convey.So(http.StatusOK, convey.ShouldEqual, loginResp.StatusCode) + convey.So(loginResp.StatusCode, convey.ShouldEqual, http.StatusOK) client.RequestEditors = append(client.RequestEditors, func(ctx context.Context, req *http.Request) error { for _, cookie := range loginResp.Cookies() { @@ -49,7 +49,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { Name: "repo", }) convey.So(err, convey.ShouldBeNil) - convey.So(http.StatusBadRequest, convey.ShouldEqual, resp.StatusCode) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) }) c.Convey("success create repo name", func() { @@ -58,20 +58,29 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { Name: "happyrun", }) convey.So(err, convey.ShouldBeNil) - convey.So(http.StatusOK, convey.ShouldEqual, resp.StatusCode) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) grp, err := api.ParseGetRepositoryResponse(resp) convey.So(err, convey.ShouldBeNil) - convey.So(controller.DefaultBranchName, convey.ShouldEqual, grp.JSON200.Head) + convey.So(grp.JSON200.Head, convey.ShouldEqual, controller.DefaultBranchName) fmt.Println(grp.JSON200.ID) //check default branch created branchResp, err := client.GetBranch(ctx, userName, grp.JSON200.Name, &api.GetBranchParams{RefName: controller.DefaultBranchName}) convey.So(err, convey.ShouldBeNil) - convey.So(http.StatusOK, convey.ShouldEqual, branchResp.StatusCode) + convey.So(branchResp.StatusCode, convey.ShouldEqual, http.StatusOK) brp, err := api.ParseGetBranchResponse(branchResp) convey.So(err, convey.ShouldBeNil) - convey.So(controller.DefaultBranchName, convey.ShouldEqual, brp.JSON200.Name) + convey.So(brp.JSON200.Name, convey.ShouldEqual, controller.DefaultBranchName) + }) + + c.Convey("add second repo ", func() { + resp, err := client.CreateRepository(ctx, api.CreateRepository{ + Description: utils.String("test resp"), + Name: "happygo", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) }) c.Convey("duplicate repo", func() { @@ -80,18 +89,40 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { Name: "happyrun", }) convey.So(err, convey.ShouldBeNil) - convey.So(http.StatusInternalServerError, convey.ShouldEqual, resp.StatusCode) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusInternalServerError) }) c.Convey("list repository", func() { - resp, err := client.ListRepository(ctx, userName) + resp, err := client.ListRepository(ctx, userName, &api.ListRepositoryParams{}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + listRepos, err := api.ParseListRepositoryResponse(resp) + convey.So(err, convey.ShouldBeNil) + + convey.So(len(*listRepos.JSON200), convey.ShouldEqual, 2) + }) + + c.Convey("list repository by prefix", func() { + resp, err := client.ListRepository(ctx, userName, &api.ListRepositoryParams{RepoPrefix: utils.String("happy")}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + listRepos, err := api.ParseListRepositoryResponse(resp) + convey.So(err, convey.ShouldBeNil) + + convey.So(len(*listRepos.JSON200), convey.ShouldEqual, 2) + }) + + c.Convey("list repository by prefix but found nothing", func() { + resp, err := client.ListRepository(ctx, userName, &api.ListRepositoryParams{RepoPrefix: utils.String("bad")}) convey.So(err, convey.ShouldBeNil) - convey.So(http.StatusOK, convey.ShouldEqual, resp.StatusCode) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) listRepos, err := api.ParseListRepositoryResponse(resp) convey.So(err, convey.ShouldBeNil) - convey.So(len(*listRepos.JSON200), convey.ShouldEqual, 1) + convey.So(len(*listRepos.JSON200), convey.ShouldEqual, 0) }) } } diff --git a/models/repo.go b/models/repo.go index c708fcfb..f3d69780 100644 --- a/models/repo.go +++ b/models/repo.go @@ -7,6 +7,15 @@ import ( "github.com/uptrace/bun" ) +type MatchMode int + +const ( + ExactMatch MatchMode = iota + PrefixMatch + SuffixMatch + LikeMatch +) + type TxOption func(*sql.TxOptions) func IsolationLevelOption(level sql.IsolationLevel) TxOption { diff --git a/models/repository.go b/models/repository.go index ee24b9ea..cb268125 100644 --- a/models/repository.go +++ b/models/repository.go @@ -11,8 +11,8 @@ import ( type Repository struct { bun.BaseModel `bun:"table:repositories"` ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` - Name string `bun:"name,unique,notnull"` - OwnerID uuid.UUID `bun:"owner_id,unique,type:uuid,notnull"` + Name string `bun:"name,unique:name_owner_unique,notnull"` + OwnerID uuid.UUID `bun:"owner_id,unique:name_owner_unique,type:uuid,notnull"` HEAD string `bun:"head,notnull"` Description *string `bun:"description"` CreatorID uuid.UUID `bun:"creator_id,type:uuid,notnull"` @@ -56,6 +56,8 @@ type ListRepoParams struct { ID uuid.UUID CreatorID uuid.UUID OwnerID uuid.UUID + Name *string + NameMatch MatchMode } func NewListRepoParams() *ListRepoParams { @@ -70,6 +72,13 @@ func (lrp *ListRepoParams) SetOwnerID(ownerID uuid.UUID) *ListRepoParams { lrp.OwnerID = ownerID return lrp } + +func (lrp *ListRepoParams) SetName(name string, match MatchMode) *ListRepoParams { + lrp.Name = &name + lrp.NameMatch = match + return lrp +} + func (lrp *ListRepoParams) SetCreatorID(creatorID uuid.UUID) *ListRepoParams { lrp.CreatorID = creatorID return lrp @@ -166,6 +175,20 @@ func (r *RepositoryRepo) List(ctx context.Context, params *ListRepoParams) ([]*R if uuid.Nil != params.OwnerID { query = query.Where("owner_id = ?", params.OwnerID) } + + if params.Name != nil { + switch params.NameMatch { + case ExactMatch: + query = query.Where("name = ?", *params.Name) + case PrefixMatch: + query = query.Where("name LIKE ?", *params.Name+"%") + case SuffixMatch: + query = query.Where("name LIKE ?", "%"+*params.Name) + case LikeMatch: + query = query.Where("name LIKE ?", "%"+*params.Name+"%") + } + } + return repos, query.Scan(ctx) } diff --git a/models/repository_test.go b/models/repository_test.go index d958b3e5..8cec45b0 100644 --- a/models/repository_test.go +++ b/models/repository_test.go @@ -21,6 +21,7 @@ func TestRepositoryRepo_Insert(t *testing.T) { repoModel := &models.Repository{} require.NoError(t, gofakeit.Struct(repoModel)) + repoModel.Name = "aaabbbb" newRepo, err := repo.Insert(ctx, repoModel) require.NoError(t, err) require.NotEqual(t, uuid.Nil, newRepo.ID) @@ -39,15 +40,47 @@ func TestRepositoryRepo_Insert(t *testing.T) { secModel := &models.Repository{} require.NoError(t, gofakeit.Struct(secModel)) secModel.CreatorID = repoModel.CreatorID + secModel.Name = "adabbeb" secRepo, err := repo.Insert(ctx, secModel) require.NoError(t, err) require.NotEqual(t, uuid.Nil, secRepo.ID) //list - repos, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID)) + repos, err := repo.List(ctx, models.NewListRepoParams()) require.NoError(t, err) require.Len(t, repos, 2) + { + //exact adabbeb + repos, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("adabbeb", models.PrefixMatch)) + require.NoError(t, err) + require.Len(t, repos, 1) + } + { + //prefix a + repos, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("a", models.PrefixMatch)) + require.NoError(t, err) + require.Len(t, repos, 2) + } + + { + //subfix b + repos, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("b", models.SuffixMatch)) + require.NoError(t, err) + require.Len(t, repos, 2) + } + { + //like ab + repos, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("ab", models.LikeMatch)) + require.NoError(t, err) + require.Len(t, repos, 2) + } + { + //like ab + repos, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("adabbeb", models.LikeMatch)) + require.NoError(t, err) + require.Len(t, repos, 1) + } //delete err = repo.Delete(ctx, models.NewDeleteRepoParams().SetID(secRepo.ID)) require.NoError(t, err) From 46855d904e52bc771014b94a98de54ea9abfab06 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Tue, 19 Dec 2023 13:53:28 +0800 Subject: [PATCH 088/210] feat: add more repository integration test --- api/custom_response.go | 6 + api/custom_response_test.go | 20 ++ auth/auth_test.go | 45 ----- auth/basic_auth.go | 39 ---- auth/context.go | 4 +- auth/types.go | 2 +- controller/repository_ctl.go | 16 +- controller/user_ctl.go | 36 +++- integrationtest/repo_test.go | 370 +++++++++++++++++++++++++++-------- integrationtest/user_test.go | 60 ++++-- 10 files changed, 404 insertions(+), 194 deletions(-) delete mode 100644 auth/auth_test.go diff --git a/api/custom_response.go b/api/custom_response.go index 70a9a11e..1e8ef069 100644 --- a/api/custom_response.go +++ b/api/custom_response.go @@ -5,6 +5,8 @@ import ( "errors" "net/http" + "github.com/jiaozifs/jiaozifs/auth" + "github.com/jiaozifs/jiaozifs/models" ) @@ -60,6 +62,10 @@ func (response *JiaozifsResponse) Error(err error) { response.NotFound() return } + if errors.Is(err, auth.ErrUserNotFound) { + response.WriteHeader(http.StatusUnauthorized) + return + } response.WriteHeader(http.StatusInternalServerError) _, _ = response.Write([]byte(err.Error())) diff --git a/api/custom_response_test.go b/api/custom_response_test.go index 04f4a07a..c09ca1a6 100644 --- a/api/custom_response_test.go +++ b/api/custom_response_test.go @@ -5,6 +5,8 @@ import ( "net/http" "testing" + "github.com/jiaozifs/jiaozifs/auth" + "github.com/jiaozifs/jiaozifs/models" "go.uber.org/mock/gomock" @@ -20,6 +22,15 @@ func TestJiaozifsResponse(t *testing.T) { jzResp.NotFound() }) + t.Run("forbidden", func(t *testing.T) { + ctrl := gomock.NewController(t) + resp := NewMockResponseWriter(ctrl) + jzResp := JiaozifsResponse{resp} + + resp.EXPECT().WriteHeader(http.StatusForbidden) + jzResp.Forbidden() + }) + t.Run("not found", func(t *testing.T) { ctrl := gomock.NewController(t) resp := NewMockResponseWriter(ctrl) @@ -75,6 +86,15 @@ func TestJiaozifsResponse(t *testing.T) { jzResp.Error(fmt.Errorf("mock %w", models.ErrNotFound)) }) + t.Run("error no auth", func(t *testing.T) { + ctrl := gomock.NewController(t) + resp := NewMockResponseWriter(ctrl) + jzResp := JiaozifsResponse{resp} + + resp.EXPECT().WriteHeader(http.StatusUnauthorized) + jzResp.Error(fmt.Errorf("mock %w", auth.ErrUserNotFound)) + }) + t.Run("string", func(t *testing.T) { ctrl := gomock.NewController(t) resp := NewMockResponseWriter(ctrl) diff --git a/auth/auth_test.go b/auth/auth_test.go deleted file mode 100644 index b1581c74..00000000 --- a/auth/auth_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package auth - -import ( - "context" - "testing" - - "github.com/jiaozifs/jiaozifs/testhelper" - - "github.com/brianvoe/gofakeit/v6" - "github.com/jiaozifs/jiaozifs/config" - "github.com/jiaozifs/jiaozifs/models" - "github.com/stretchr/testify/require" -) - -func TestLogin_Success(t *testing.T) { - ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint - // repo - mockRepo := models.NewUserRepo(db) - // config - mockConfig := &config.AuthConfig{SecretKey: []byte("THIS_MUST_BE_CHANGED_IN_PRODUCTION")} - // user - userModel := &models.User{} - require.NoError(t, gofakeit.Struct(userModel)) - - // registration - register := &Register{ - Username: userModel.Name, - Email: userModel.Email, - Password: userModel.EncryptedPassword, - } - err := register.Register(ctx, mockRepo) - require.NoError(t, err) - - // login - login := &Login{ - Username: userModel.Name, - Password: userModel.EncryptedPassword, - } - token, err := login.Login(context.Background(), mockRepo, mockConfig) - require.NoError(t, err, "Login should not return an error") - require.NotEmpty(t, token.Token, "Token should not be empty") - require.NotNil(t, token.TokenExpiration, "Token expiration should not be nil") -} diff --git a/auth/basic_auth.go b/auth/basic_auth.go index ea047a38..4d6dc45f 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -5,52 +5,13 @@ import ( "fmt" "time" - "github.com/jiaozifs/jiaozifs/config" - - "github.com/go-openapi/swag" logging "github.com/ipfs/go-log/v2" - "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/models" "golang.org/x/crypto/bcrypt" ) var log = logging.Logger("auth") -type Login struct { - Username string `json:"username"` - Password string `json:"password"` -} - -func (l *Login) Login(ctx context.Context, repo models.IUserRepo, config *config.AuthConfig) (token api.AuthenticationToken, err error) { - // get user encryptedPassword by username - ep, err := repo.GetEPByName(ctx, l.Username) - if err != nil { - return token, fmt.Errorf("cannt get user %s encrypt password %w", l.Username, err) - } - - // Compare ep and password - err = bcrypt.CompareHashAndPassword([]byte(ep), []byte(l.Password)) - if err != nil { - log.Errorf("password err: %s", err) - return token, fmt.Errorf("user %s password not match %w", l.Username, err) - } - // Generate user token - loginTime := time.Now() - expires := loginTime.Add(expirationDuration) - secretKey := config.SecretKey - - tokenString, err := GenerateJWTLogin(secretKey, l.Username, loginTime, expires) - if err != nil { - return token, fmt.Errorf("generate token err: %w", err) - } - - log.Infof("usert %s login successful", l.Username) - - token.Token = tokenString - token.TokenExpiration = swag.Int64(expires.Unix()) - return token, nil -} - type Register struct { Username string `json:"username"` Email string `json:"email"` diff --git a/auth/context.go b/auth/context.go index 2ee8f147..230da7a4 100644 --- a/auth/context.go +++ b/auth/context.go @@ -7,6 +7,8 @@ import ( "github.com/jiaozifs/jiaozifs/models" ) +var ErrUserNotFound = fmt.Errorf("UserNotFound") + type contextKey string const ( @@ -16,7 +18,7 @@ const ( func GetOperator(ctx context.Context) (*models.User, error) { user, ok := ctx.Value(userContextKey).(*models.User) if !ok { - return nil, fmt.Errorf("UserNotFound") + return nil, ErrUserNotFound } return user, nil } diff --git a/auth/types.go b/auth/types.go index badea89f..0262186e 100644 --- a/auth/types.go +++ b/auth/types.go @@ -3,6 +3,6 @@ package auth import "time" const ( - expirationDuration = time.Hour + ExpirationDuration = time.Hour passwordCost = 12 ) diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index 0da13697..a3761adf 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -29,7 +29,8 @@ const DefaultBranchName = "main" var maxNameLength = 20 var alphanumeric = regexp.MustCompile("^[a-zA-Z0-9_]*$") -var RepoNameBlackList = []string{"repository", "repo", "user", "users"} +// RepoNameBlackList forbid repo name, reserve for routes +var RepoNameBlackList = []string{"repository", "repositories", "wip", "wips", "object", "objects", "commit", "commits", "ref", "refs", "repo", "repos", "user", "users"} func CheckRepositoryName(name string) error { for _, blackName := range RepoNameBlackList { @@ -162,7 +163,13 @@ func (repositoryCtl RepositoryController) DeleteRepository(ctx context.Context, return } - if operator.Name != ownerName { + owner, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + if operator.Name != owner.Name { w.Forbidden() return } @@ -273,6 +280,11 @@ func (repositoryCtl RepositoryController) GetCommitsInRepository(ctx context.Con return } + if ref.CommitHash.IsEmpty() { + w.JSON([]api.Commit{}) + return + } + commit, err := repositoryCtl.Repo.CommitRepo().Commit(ctx, ref.CommitHash) if err != nil { w.Error(err) diff --git a/controller/user_ctl.go b/controller/user_ctl.go index 168443d3..b02477db 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -3,6 +3,10 @@ package controller import ( "context" "net/http" + "time" + + "github.com/go-openapi/swag" + "golang.org/x/crypto/bcrypt" openapitypes "github.com/oapi-codegen/runtime/types" @@ -33,27 +37,45 @@ type UserController struct { } func (userCtl UserController) Login(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, body api.LoginJSONRequestBody) { - login := auth.Login{ - Username: body.Username, - Password: body.Password, + + // get user encryptedPassword by username + ep, err := userCtl.Repo.UserRepo().GetEPByName(ctx, body.Username) + if err != nil { + w.Code(http.StatusUnauthorized) + return } - // perform login - authToken, err := login.Login(ctx, userCtl.Repo.UserRepo(), userCtl.Config) + // Compare ep and password + err = bcrypt.CompareHashAndPassword([]byte(ep), []byte(body.Password)) + if err != nil { + w.Code(http.StatusUnauthorized) + return + } + // Generate user token + loginTime := time.Now() + expires := loginTime.Add(auth.ExpirationDuration) + secretKey := userCtl.Config.SecretKey + + tokenString, err := auth.GenerateJWTLogin(secretKey, body.Username, loginTime, expires) if err != nil { w.Error(err) return } + userCtlLog.Infof("usert %s login successful", body.Username) + internalAuthSession, _ := userCtl.SessionStore.Get(r, auth.InternalAuthSessionName) - internalAuthSession.Values[auth.TokenSessionKeyName] = authToken.Token + internalAuthSession.Values[auth.TokenSessionKeyName] = tokenString err = userCtl.SessionStore.Save(r, w, internalAuthSession) if err != nil { userCtlLog.Errorf("Failed to save internal auth session %v", err) w.Code(http.StatusInternalServerError) return } - w.JSON(authToken) + w.JSON(api.AuthenticationToken{ + Token: tokenString, + TokenExpiration: swag.Int64(expires.Unix()), + }) } func (userCtl UserController) Register(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.RegisterJSONRequestBody) { diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index c1e839d3..3a7b5ddc 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -19,110 +19,324 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { client, _ := api.NewClient(urlStr + apiimpl.APIV1Prefix) return func(c convey.C) { userName := "jimmy" - c.Convey("create new user for repo", func() { - resp, err := client.Register(ctx, api.RegisterJSONRequestBody{ - Username: userName, - Password: "12345678", - Email: "mock@gmail.com", + CreateUser(ctx, c, client, userName) + LoginAndSwitch(ctx, c, client, userName) + + c.Convey("create repo", func(c convey.C) { + c.Convey("forbidden create repo name", func() { + resp, err := client.CreateRepository(ctx, api.CreateRepository{ + Description: utils.String("test resp"), + Name: "repo", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) - loginResp, err := client.Login(ctx, api.LoginJSONRequestBody{ - Username: userName, - Password: "12345678", + c.Convey("success create repo name", func() { + resp, err := client.CreateRepository(ctx, api.CreateRepository{ + Description: utils.String("test resp"), + Name: "happyrun", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + grp, err := api.ParseGetRepositoryResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(grp.JSON200.Head, convey.ShouldEqual, controller.DefaultBranchName) + fmt.Println(grp.JSON200.ID) + //check default branch created + branchResp, err := client.GetBranch(ctx, userName, grp.JSON200.Name, &api.GetBranchParams{RefName: controller.DefaultBranchName}) + convey.So(err, convey.ShouldBeNil) + convey.So(branchResp.StatusCode, convey.ShouldEqual, http.StatusOK) + + brp, err := api.ParseGetBranchResponse(branchResp) + convey.So(err, convey.ShouldBeNil) + convey.So(brp.JSON200.Name, convey.ShouldEqual, controller.DefaultBranchName) }) - convey.So(err, convey.ShouldBeNil) - convey.So(loginResp.StatusCode, convey.ShouldEqual, http.StatusOK) - client.RequestEditors = append(client.RequestEditors, func(ctx context.Context, req *http.Request) error { - for _, cookie := range loginResp.Cookies() { - req.AddCookie(cookie) - } - return nil + c.Convey("add second repo ", func() { + resp, err := client.CreateRepository(ctx, api.CreateRepository{ + Description: utils.String("test resp"), + Name: "happygo", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) }) - }) - c.Convey("forbidden create repo name", func() { - resp, err := client.CreateRepository(ctx, api.CreateRepository{ - Description: utils.String("test resp"), - Name: "repo", + c.Convey("duplicate repo name", func() { + resp, err := client.CreateRepository(ctx, api.CreateRepository{ + Description: utils.String("test resp"), + Name: "happyrun", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusInternalServerError) + }) + + c.Convey("invalid repo name", func() { + resp, err := client.CreateRepository(ctx, api.CreateRepository{ + Description: utils.String("test resp"), + Name: "happyrun1@#%", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) - }) - c.Convey("success create repo name", func() { - resp, err := client.CreateRepository(ctx, api.CreateRepository{ - Description: utils.String("test resp"), - Name: "happyrun", - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) - - grp, err := api.ParseGetRepositoryResponse(resp) - convey.So(err, convey.ShouldBeNil) - convey.So(grp.JSON200.Head, convey.ShouldEqual, controller.DefaultBranchName) - fmt.Println(grp.JSON200.ID) - //check default branch created - branchResp, err := client.GetBranch(ctx, userName, grp.JSON200.Name, &api.GetBranchParams{RefName: controller.DefaultBranchName}) - convey.So(err, convey.ShouldBeNil) - convey.So(branchResp.StatusCode, convey.ShouldEqual, http.StatusOK) - - brp, err := api.ParseGetBranchResponse(branchResp) - convey.So(err, convey.ShouldBeNil) - convey.So(brp.JSON200.Name, convey.ShouldEqual, controller.DefaultBranchName) + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.CreateRepository(ctx, api.CreateRepository{ + Description: utils.String("test resp"), + Name: "happyrun2", + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) }) - c.Convey("add second repo ", func() { - resp, err := client.CreateRepository(ctx, api.CreateRepository{ - Description: utils.String("test resp"), - Name: "happygo", + c.Convey("list repository", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.ListRepositoryOfAuthenticatedUser(ctx) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("list repository in authenticated user", func() { + resp, err := client.ListRepositoryOfAuthenticatedUser(ctx) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + listRepos, err := api.ParseListRepositoryResponse(resp) + convey.So(err, convey.ShouldBeNil) + + convey.So(len(*listRepos.JSON200), convey.ShouldEqual, 2) + }) + + c.Convey("list repository", func() { + resp, err := client.ListRepository(ctx, userName, &api.ListRepositoryParams{}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + listRepos, err := api.ParseListRepositoryResponse(resp) + convey.So(err, convey.ShouldBeNil) + + convey.So(len(*listRepos.JSON200), convey.ShouldEqual, 2) + }) + + c.Convey("list repository by prefix", func() { + resp, err := client.ListRepository(ctx, userName, &api.ListRepositoryParams{RepoPrefix: utils.String("happy")}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + listRepos, err := api.ParseListRepositoryResponse(resp) + convey.So(err, convey.ShouldBeNil) + + convey.So(len(*listRepos.JSON200), convey.ShouldEqual, 2) + }) + + c.Convey("list repository by prefix but found nothing", func() { + resp, err := client.ListRepository(ctx, userName, &api.ListRepositoryParams{RepoPrefix: utils.String("bad")}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + listRepos, err := api.ParseListRepositoryResponse(resp) + convey.So(err, convey.ShouldBeNil) + + convey.So(len(*listRepos.JSON200), convey.ShouldEqual, 0) + }) + + c.Convey("list others repository", func() { + resp, err := client.ListRepository(ctx, "admin", &api.ListRepositoryParams{RepoPrefix: utils.String("bad")}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) }) - c.Convey("duplicate repo", func() { - resp, err := client.CreateRepository(ctx, api.CreateRepository{ - Description: utils.String("test resp"), - Name: "happyrun", + c.Convey("get repository", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.GetRepository(ctx, userName, "happyrun") + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("get repository", func() { + resp, err := client.GetRepository(ctx, userName, "happyrun") + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + getResult, err := api.ParseGetRepositoryResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(getResult.JSON200.Name, convey.ShouldEqual, "happyrun") + }) + + c.Convey("get not exit repo", func() { + resp, err := client.GetRepository(ctx, userName, "happyrun_mock") + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("get from non exit user", func() { + resp, err := client.GetRepository(ctx, "telo", "happyrun") + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("get other's repo", func() { + resp, err := client.GetRepository(ctx, "admin", "happyrun") + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusInternalServerError) }) - c.Convey("list repository", func() { - resp, err := client.ListRepository(ctx, userName, &api.ListRepositoryParams{}) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + c.Convey("update repository", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.UpdateRepository(ctx, userName, "happyrun", api.UpdateRepositoryJSONRequestBody{ + Description: utils.String(""), + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("success update repository", func() { + description := "mock description" + resp, err := client.UpdateRepository(ctx, userName, "happyrun", api.UpdateRepositoryJSONRequestBody{ + Description: utils.String(description), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) - listRepos, err := api.ParseListRepositoryResponse(resp) - convey.So(err, convey.ShouldBeNil) + getResp, err := client.GetRepository(ctx, userName, "happyrun") + convey.So(err, convey.ShouldBeNil) + convey.So(getResp.StatusCode, convey.ShouldEqual, http.StatusOK) - convey.So(len(*listRepos.JSON200), convey.ShouldEqual, 2) + getResult, err := api.ParseGetRepositoryResponse(getResp) + convey.So(err, convey.ShouldBeNil) + convey.So(*getResult.JSON200.Description, convey.ShouldEqual, description) + }) + + c.Convey("update repository in not exit repo", func() { + description := "" + resp, err := client.UpdateRepository(ctx, userName, "happyrunfake", api.UpdateRepositoryJSONRequestBody{ + Description: utils.String(description), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("update repository in non exit user", func() { + description := "" + resp, err := client.UpdateRepository(ctx, "telo", "happyrun", api.UpdateRepositoryJSONRequestBody{ + Description: utils.String(description), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("update repository in other's repo", func() { + description := "" + resp, err := client.UpdateRepository(ctx, "admin", "happyrun", api.UpdateRepositoryJSONRequestBody{ + Description: utils.String(description), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) }) - c.Convey("list repository by prefix", func() { - resp, err := client.ListRepository(ctx, userName, &api.ListRepositoryParams{RepoPrefix: utils.String("happy")}) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + c.Convey("get commits in repository", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.GetCommitsInRepository(ctx, userName, "happyrun", &api.GetCommitsInRepositoryParams{ + RefName: utils.String(controller.DefaultBranchName), + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) - listRepos, err := api.ParseListRepositoryResponse(resp) - convey.So(err, convey.ShouldBeNil) + c.Convey("success get commits", func() { + resp, err := client.GetCommitsInRepository(ctx, userName, "happyrun", &api.GetCommitsInRepositoryParams{ + RefName: utils.String(controller.DefaultBranchName), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) - convey.So(len(*listRepos.JSON200), convey.ShouldEqual, 2) + result, err := api.ParseGetCommitsInRepositoryResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(result.JSON200, convey.ShouldNotBeNil) + convey.So(len(*result.JSON200), convey.ShouldEqual, 0) + }) + + c.Convey("update repository in not exit repo", func() { + resp, err := client.GetCommitsInRepository(ctx, userName, "happyrunfake", &api.GetCommitsInRepositoryParams{ + RefName: utils.String(controller.DefaultBranchName), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("update repository in non exit user", func() { + resp, err := client.GetCommitsInRepository(ctx, "telo", "happyrun", &api.GetCommitsInRepositoryParams{ + RefName: utils.String(controller.DefaultBranchName), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("update repository in other's repo", func() { + resp, err := client.GetCommitsInRepository(ctx, "admin", "happyrun", &api.GetCommitsInRepositoryParams{ + RefName: utils.String(controller.DefaultBranchName), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) }) - c.Convey("list repository by prefix but found nothing", func() { - resp, err := client.ListRepository(ctx, userName, &api.ListRepositoryParams{RepoPrefix: utils.String("bad")}) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + c.Convey("delete repository", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.DeleteRepository(ctx, userName, "happyrun") + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + c.Convey("delete repository in not exit repo", func() { + resp, err := client.DeleteRepository(ctx, userName, "happyrunfake") + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) - listRepos, err := api.ParseListRepositoryResponse(resp) - convey.So(err, convey.ShouldBeNil) + c.Convey("delete repository in non exit user", func() { + resp, err := client.DeleteRepository(ctx, "telo", "happyrun") + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) - convey.So(len(*listRepos.JSON200), convey.ShouldEqual, 0) + c.Convey("delete repository in other's repo", func() { + resp, err := client.DeleteRepository(ctx, "admin", "happyrun") + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + + c.Convey("delete repository successful", func() { + resp, err := client.DeleteRepository(ctx, userName, "happyrun") + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + getResp, err := client.GetRepository(ctx, userName, "happyrun") + convey.So(err, convey.ShouldBeNil) + convey.So(getResp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) }) } } diff --git a/integrationtest/user_test.go b/integrationtest/user_test.go index 94ad648f..236fde35 100644 --- a/integrationtest/user_test.go +++ b/integrationtest/user_test.go @@ -14,42 +14,60 @@ func UserSpec(ctx context.Context, urlStr string) func(c convey.C) { client, _ := api.NewClient(urlStr + apiimpl.APIV1Prefix) return func(c convey.C) { userName := "admin" - c.Convey("register", func() { - resp, err := client.Register(ctx, api.RegisterJSONRequestBody{ - Username: userName, - Password: "12345678", - Email: "mock@gmail.com", - }) - convey.So(err, convey.ShouldBeNil) - convey.So(http.StatusOK, convey.ShouldEqual, resp.StatusCode) - }) + CreateUser(ctx, c, client, userName) c.Convey("usr profile no cookie", func() { resp, err := client.GetUserInfo(ctx) convey.So(err, convey.ShouldBeNil) - convey.So(http.StatusForbidden, convey.ShouldEqual, resp.StatusCode) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) }) - c.Convey("login", func() { + c.Convey("login fail", func() { resp, err := client.Login(ctx, api.LoginJSONRequestBody{ Username: "admin", - Password: "12345678", + Password: " vvvvvvvv", }) convey.So(err, convey.ShouldBeNil) - convey.So(http.StatusOK, convey.ShouldEqual, resp.StatusCode) - - client.RequestEditors = append(client.RequestEditors, func(ctx context.Context, req *http.Request) error { - for _, cookie := range resp.Cookies() { - req.AddCookie(cookie) - } - return nil - }) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) + LoginAndSwitch(ctx, c, client, userName) + c.Convey("usr profile", func() { resp, err := client.GetUserInfo(ctx) convey.So(err, convey.ShouldBeNil) - convey.So(http.StatusOK, convey.ShouldEqual, resp.StatusCode) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) }) } } + +func CreateUser(ctx context.Context, c convey.C, client *api.Client, userName string) { + c.Convey("register "+userName, func() { + resp, err := client.Register(ctx, api.RegisterJSONRequestBody{ + Username: userName, + Password: "12345678", + Email: "mock@gmail.com", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + }) +} + +func LoginAndSwitch(ctx context.Context, c convey.C, client *api.Client, userName string) { + c.Convey("login "+userName, func() { + resp, err := client.Login(ctx, api.LoginJSONRequestBody{ + Username: userName, + Password: "12345678", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + client.RequestEditors = nil + client.RequestEditors = append(client.RequestEditors, func(ctx context.Context, req *http.Request) error { + for _, cookie := range resp.Cookies() { + req.AddCookie(cookie) + } + return nil + }) + }) +} From 1d5e020a7e9d433c9a4a504da9a32deef866079f Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Tue, 19 Dec 2023 14:35:35 +0800 Subject: [PATCH 089/210] fix: init home fail --- integrationtest/branch_test.go | 1 + 1 file changed, 1 insertion(+) create mode 100644 integrationtest/branch_test.go diff --git a/integrationtest/branch_test.go b/integrationtest/branch_test.go new file mode 100644 index 00000000..a74af46a --- /dev/null +++ b/integrationtest/branch_test.go @@ -0,0 +1 @@ +package integrationtest From 8bc91d738e462371c7cc8dd9eb31684baf7d2d47 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Tue, 19 Dec 2023 14:36:48 +0800 Subject: [PATCH 090/210] fix: init at home fail --- auth/session_store.go | 10 ++++-- cmd/root.go | 2 +- config/config.go | 64 +++++++++++++++++++++------------- config/default.go | 8 +++-- controller/user_ctl.go | 7 +++- integrationtest/branch_test.go | 29 +++++++++++++++ integrationtest/repo_test.go | 14 ++++++-- integrationtest/root_test.go | 1 + integrationtest/user_test.go | 8 ++--- 9 files changed, 107 insertions(+), 36 deletions(-) diff --git a/auth/session_store.go b/auth/session_store.go index e15f2ab9..caae43eb 100644 --- a/auth/session_store.go +++ b/auth/session_store.go @@ -1,6 +1,8 @@ package auth import ( + "encoding/hex" + "github.com/gorilla/sessions" "github.com/jiaozifs/jiaozifs/auth/crypt" "github.com/jiaozifs/jiaozifs/config" @@ -10,6 +12,10 @@ func NewSessionStore(secretStrore crypt.SecretStore) sessions.Store { return sessions.NewCookieStore(secretStrore.SharedSecret()) } -func NewSectetStore(authConfig *config.AuthConfig) crypt.SecretStore { - return crypt.NewSecretStore(authConfig.SecretKey) +func NewSectetStore(authConfig *config.AuthConfig) (crypt.SecretStore, error) { + secretKey, err := hex.DecodeString(authConfig.SecretKey) + if err != nil { + return nil, err + } + return crypt.NewSecretStore(secretKey), nil } diff --git a/cmd/root.go b/cmd/root.go index af58b1ba..fb796249 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -30,7 +30,7 @@ func RootCmd() *cobra.Command { return rootCmd } func init() { - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.jiaozifs/config.yaml)") + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "~/.jiaozifs/config.toml", "config file (default is $HOME/.jiaozifs/config.toml)") rootCmd.PersistentFlags().String("listen", config.DefaultLocalBSPath, "config blockstore path") _ = viper.BindPFlag("api.listen", rootCmd.PersistentFlags().Lookup("listen")) _ = viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config")) diff --git a/config/config.go b/config/config.go index f8a8f192..163eca14 100644 --- a/config/config.go +++ b/config/config.go @@ -5,6 +5,8 @@ import ( "os" "path" + "github.com/mitchellh/go-homedir" + ms "github.com/mitchellh/mapstructure" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -39,7 +41,7 @@ type DatabaseConfig struct { } type AuthConfig struct { - SecretKey []byte `mapstructure:"secretKey"` + SecretKey string `mapstructure:"secretKey"` UIConfig struct { RBAC string `mapstructure:"rbac"` @@ -52,33 +54,47 @@ type AuthConfig struct { } `mapstructure:"ui_config"` } -func InitConfig(cfgPath string) error { - // Search config in home directory with name ".jiaozifs" (without extension). - viper.AddConfigPath(cfgPath) - viper.SetConfigType("toml") - viper.SetConfigName("config") - if len(viper.ConfigFileUsed()) == 0 { - data := make(map[string]interface{}) - err := ms.Decode(defaultCfg, &data) - if err != nil { - return err - } - for k, v := range data { - viper.SetDefault(k, v) - } - - basePath := path.Dir(cfgPath) - err = os.MkdirAll(basePath, 0755) - if err != nil { - return err - } - return viper.WriteConfigAs(cfgPath) +func InitConfig(cfgFile string) error { + var err error + cfgFile, err = homedir.Expand(cfgFile) + if err != nil { + return err + } + + _, err = os.Stat(cfgFile) + if err == nil { + return fmt.Errorf("config already exit in %s", cfgFile) + } + if err != nil && !os.IsNotExist(err) { + return err + } + + viper.SetConfigFile(cfgFile) + + data := make(map[string]interface{}) + err = ms.Decode(defaultCfg, &data) + if err != nil { + return err + } + for k, v := range data { + viper.SetDefault(k, v) + } + + basePath := path.Dir(cfgFile) + err = os.MkdirAll(basePath, 0755) + if err != nil { + return err } - return fmt.Errorf("config already exit in %s", cfgPath) + return viper.WriteConfigAs(cfgFile) } // LoadConfig reads in config file and ENV variables if set. func LoadConfig(cfgFile string) (*Config, error) { + var err error + cfgFile, err = homedir.Expand(cfgFile) + if err != nil { + return nil, err + } if len(cfgFile) > 0 { // Use config file from the flag. viper.SetConfigFile(cfgFile) @@ -96,7 +112,7 @@ func LoadConfig(cfgFile string) (*Config, error) { viper.AutomaticEnv() // read in environment variables that match // If a config file is found, read it in. - err := viper.ReadInConfig() + err = viper.ReadInConfig() if err != nil { return nil, err } diff --git a/config/default.go b/config/default.go index e05d3e00..c6af924e 100644 --- a/config/default.go +++ b/config/default.go @@ -1,6 +1,10 @@ package config -import "github.com/jiaozifs/jiaozifs/utils" +import ( + "encoding/hex" + + "github.com/jiaozifs/jiaozifs/utils" +) var DefaultLocalBSPath = "~/.jiaozifs/blockstore" @@ -28,7 +32,7 @@ var defaultCfg = Config{ }{Path: DefaultLocalBSPath, ImportEnabled: false, ImportHidden: false, AllowedExternalPrefixes: nil}), }, Auth: AuthConfig{ - SecretKey: []byte("THIS_MUST_BE_CHANGED_IN_PRODUCTION"), + SecretKey: hex.EncodeToString([]byte("THIS_MUST_BE_CHANGED_IN_PRODUCTION")), UIConfig: struct { RBAC string `mapstructure:"rbac"` LoginURL string `mapstructure:"login_url"` diff --git a/controller/user_ctl.go b/controller/user_ctl.go index b02477db..e1055253 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -2,6 +2,7 @@ package controller import ( "context" + "encoding/hex" "net/http" "time" @@ -54,7 +55,11 @@ func (userCtl UserController) Login(ctx context.Context, w *api.JiaozifsResponse // Generate user token loginTime := time.Now() expires := loginTime.Add(auth.ExpirationDuration) - secretKey := userCtl.Config.SecretKey + secretKey, err := hex.DecodeString(userCtl.Config.SecretKey) + if err != nil { + w.Error(err) + return + } tokenString, err := auth.GenerateJWTLogin(secretKey, body.Username, loginTime, expires) if err != nil { diff --git a/integrationtest/branch_test.go b/integrationtest/branch_test.go index a74af46a..1b5d49b2 100644 --- a/integrationtest/branch_test.go +++ b/integrationtest/branch_test.go @@ -1 +1,30 @@ package integrationtest + +import ( + "context" + "net/http" + + "github.com/jiaozifs/jiaozifs/api" + apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" + "github.com/smartystreets/goconvey/convey" +) + +func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { + return func(c convey.C) { + client, _ := api.NewClient(urlStr + apiimpl.APIV1Prefix) + userName := "mike" + repoName := "mlops" + createUser(ctx, c, client, userName) + loginAndSwitch(ctx, c, client, userName) + createRepo(ctx, c, client, repoName) + c.Convey("create branch", func() { + c.Convey("success create branch", func() { + resp, err := client.CreateBranch(ctx, userName, repoName, api.CreateBranchJSONRequestBody{ + Name: "feat/test", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + }) + }) + } +} diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index 3a7b5ddc..cd997204 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -19,8 +19,8 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { client, _ := api.NewClient(urlStr + apiimpl.APIV1Prefix) return func(c convey.C) { userName := "jimmy" - CreateUser(ctx, c, client, userName) - LoginAndSwitch(ctx, c, client, userName) + createUser(ctx, c, client, userName) + loginAndSwitch(ctx, c, client, userName) c.Convey("create repo", func(c convey.C) { c.Convey("forbidden create repo name", func() { @@ -340,3 +340,13 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { }) } } + +func createRepo(ctx context.Context, c convey.C, client *api.Client, repoName string) { + c.Convey("create repo "+repoName, func() { + resp, err := client.CreateRepository(ctx, api.CreateRepositoryJSONRequestBody{ + Name: repoName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + }) +} diff --git a/integrationtest/root_test.go b/integrationtest/root_test.go index dcea5fe1..5219617c 100644 --- a/integrationtest/root_test.go +++ b/integrationtest/root_test.go @@ -14,4 +14,5 @@ func TestSpec(t *testing.T) { convey.Convey("user test", t, UserSpec(ctx, urlStr)) convey.Convey("repo test", t, RepoSpec(ctx, urlStr)) + convey.Convey("branch test", t, BranchSpec(ctx, urlStr)) } diff --git a/integrationtest/user_test.go b/integrationtest/user_test.go index 236fde35..f51b268d 100644 --- a/integrationtest/user_test.go +++ b/integrationtest/user_test.go @@ -14,7 +14,7 @@ func UserSpec(ctx context.Context, urlStr string) func(c convey.C) { client, _ := api.NewClient(urlStr + apiimpl.APIV1Prefix) return func(c convey.C) { userName := "admin" - CreateUser(ctx, c, client, userName) + createUser(ctx, c, client, userName) c.Convey("usr profile no cookie", func() { resp, err := client.GetUserInfo(ctx) @@ -31,7 +31,7 @@ func UserSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) - LoginAndSwitch(ctx, c, client, userName) + loginAndSwitch(ctx, c, client, userName) c.Convey("usr profile", func() { resp, err := client.GetUserInfo(ctx) @@ -41,7 +41,7 @@ func UserSpec(ctx context.Context, urlStr string) func(c convey.C) { } } -func CreateUser(ctx context.Context, c convey.C, client *api.Client, userName string) { +func createUser(ctx context.Context, c convey.C, client *api.Client, userName string) { c.Convey("register "+userName, func() { resp, err := client.Register(ctx, api.RegisterJSONRequestBody{ Username: userName, @@ -53,7 +53,7 @@ func CreateUser(ctx context.Context, c convey.C, client *api.Client, userName st }) } -func LoginAndSwitch(ctx context.Context, c convey.C, client *api.Client, userName string) { +func loginAndSwitch(ctx context.Context, c convey.C, client *api.Client, userName string) { c.Convey("login "+userName, func() { resp, err := client.Login(ctx, api.LoginJSONRequestBody{ Username: userName, From ffbc5d583bd00507091815b718e8414b86990cbb Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Tue, 19 Dec 2023 16:20:38 +0800 Subject: [PATCH 091/210] test: add branch integration test --- cmd/init.go | 3 +- controller/branch_ctl.go | 64 +++++++++- integrationtest/branch_test.go | 215 ++++++++++++++++++++++++++++++++- integrationtest/repo_test.go | 43 +++---- models/commit.go | 6 +- models/ref.go | 12 +- models/repository.go | 12 +- models/tree.go | 24 +++- models/user.go | 6 +- models/wip.go | 16 ++- utils/hash/hash.go | 2 + 11 files changed, 359 insertions(+), 44 deletions(-) diff --git a/cmd/init.go b/cmd/init.go index 25fbe588..f1f1010a 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -1,7 +1,6 @@ package cmd import ( - "fmt" "os" "github.com/jiaozifs/jiaozifs/config" @@ -38,7 +37,7 @@ var initCmd = &cobra.Command{ if err != nil { return err } - fmt.Println(cfg.API.Listen) + if cfg.Blockstore.Type == "local" { _, err = os.Stat(cfg.Blockstore.Local.Path) if os.IsNotExist(err) { diff --git a/controller/branch_ctl.go b/controller/branch_ctl.go index af82aa07..841adc75 100644 --- a/controller/branch_ctl.go +++ b/controller/branch_ctl.go @@ -3,9 +3,14 @@ package controller import ( "context" "errors" + "fmt" "net/http" + "regexp" + "strings" "time" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/jiaozifs/jiaozifs/auth" "github.com/jiaozifs/jiaozifs/api" @@ -13,6 +18,31 @@ import ( "go.uber.org/fx" ) +var maxBranchNameLength = 20 +var branchNameRegex = regexp.MustCompile("^[a-zA-Z0-9_]*$") + +func CheckBranchName(name string) error { + for _, blackName := range RepoNameBlackList { + if name == blackName { + return errors.New("repository name is black list") + } + } + + if len(name) > maxBranchNameLength { + return fmt.Errorf("branch name is too long") + } + + seg := strings.Split(name, "/") + if len(seg) > 2 { + return fmt.Errorf("ref format must be or /") + } + + if !branchNameRegex.Match([]byte(seg[0])) || !branchNameRegex.Match([]byte(seg[1])) { + return fmt.Errorf("branch name must be combination of number and letter or combine with '/'") + } + return nil +} + type BranchController struct { fx.In @@ -60,6 +90,11 @@ func (bct BranchController) ListBranches(ctx context.Context, w *api.JiaozifsRes } func (bct BranchController) CreateBranch(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.CreateBranchJSONRequestBody, ownerName string, repositoryName string) { + if err := CheckBranchName(body.Name); err != nil { + w.BadRequest(err.Error()) + return + } + operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) @@ -84,18 +119,33 @@ func (bct BranchController) CreateBranch(ctx context.Context, w *api.JiaozifsRes return } - // Get source ref - ref, err := bct.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(body.Name).SetRepositoryID(repository.ID)) + //check exit + _, err = bct.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(body.Name).SetRepositoryID(repository.ID)) + if err == nil { + w.BadRequest(fmt.Sprintf("%s already exit", body.Name)) + return + } if err != nil && !errors.Is(err, models.ErrNotFound) { w.Error(err) return } + //get source ref + sourceRef, err := bct.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(body.Source).SetRepositoryID(repository.ID)) + if err != nil && !errors.Is(err, models.ErrNotFound) { + w.Error(err) + return + } + + commitHash := hash.EmptyHash + if sourceRef != nil { + commitHash = sourceRef.CommitHash + } + // Create branch newRef := &models.Ref{ RepositoryID: repository.ID, - CommitHash: ref.CommitHash, + CommitHash: commitHash, Name: body.Name, - Description: ref.Description, CreatorID: operator.ID, CreatedAt: time.Now(), UpdatedAt: time.Now(), @@ -142,6 +192,12 @@ func (bct BranchController) DeleteBranch(ctx context.Context, w *api.JiaozifsRes return } + _, err = bct.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(params.RefName).SetRepositoryID(repository.ID)) + if err != nil { + w.Error(err) + return + } + // Delete branch err = bct.Repo.RefRepo().Delete(ctx, models.NewDeleteRefParams().SetName(params.RefName).SetRepositoryID(repository.ID)) if err != nil { diff --git a/integrationtest/branch_test.go b/integrationtest/branch_test.go index 1b5d49b2..6e28f3ee 100644 --- a/integrationtest/branch_test.go +++ b/integrationtest/branch_test.go @@ -10,21 +10,230 @@ import ( ) func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { + client, _ := api.NewClient(urlStr + apiimpl.APIV1Prefix) return func(c convey.C) { - client, _ := api.NewClient(urlStr + apiimpl.APIV1Prefix) userName := "mike" repoName := "mlops" + refName := "feat/test" + createUser(ctx, c, client, userName) loginAndSwitch(ctx, c, client, userName) createRepo(ctx, c, client, repoName) - c.Convey("create branch", func() { + + c.Convey("create branch", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.CreateBranch(ctx, userName, repoName, api.CreateBranchJSONRequestBody{ + Name: "feat/no_auth", + Source: "main", + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + c.Convey("success create branch", func() { resp, err := client.CreateBranch(ctx, userName, repoName, api.CreateBranchJSONRequestBody{ - Name: "feat/test", + Name: refName, + Source: "main", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + }) + + c.Convey("fail to create branch in non exit repo", func() { + resp, err := client.CreateBranch(ctx, userName, "fakerepo", api.CreateBranchJSONRequestBody{ + Name: "feat/test", + Source: "main", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("too long name", func() { + resp, err := client.CreateBranch(ctx, userName, repoName, api.CreateBranchJSONRequestBody{ + Name: "feat/aaaaaaaaaaaaaaaaa", + Source: "main", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + + c.Convey("invalid format", func() { + resp, err := client.CreateBranch(ctx, userName, repoName, api.CreateBranchJSONRequestBody{ + Name: "feat/aaaa/aaa", + Source: "main", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + + c.Convey("invalid char", func() { + resp, err := client.CreateBranch(ctx, userName, repoName, api.CreateBranchJSONRequestBody{ + Name: "feat&*", + Source: "main", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + + c.Convey("forbidden create branch in others", func() { + resp, err := client.CreateBranch(ctx, "jimmy", "happygo", api.CreateBranchJSONRequestBody{ + Name: "feat/test", + Source: "main", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + }) + + c.Convey("get branch", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.GetBranch(ctx, userName, repoName, &api.GetBranchParams{ + RefName: refName, + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("success get branch", func() { + resp, err := client.GetBranch(ctx, userName, repoName, &api.GetBranchParams{ + RefName: refName, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + respResult, err := api.ParseGetBranchResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(respResult.JSON200.Name, convey.ShouldEqual, refName) + }) + + c.Convey("fail to get non exit ref", func() { + resp, err := client.GetBranch(ctx, userName, repoName, &api.GetBranchParams{ + RefName: "mock_ref", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to get ref from non exit user", func() { + resp, err := client.GetBranch(ctx, "mock_owner", repoName, &api.GetBranchParams{ + RefName: "main", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to get non exit branch", func() { + resp, err := client.GetBranch(ctx, userName, "mock_repo", &api.GetBranchParams{ + RefName: "main", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to others ref", func() { + resp, err := client.GetBranch(ctx, "jimmy", "happygo", &api.GetBranchParams{ + RefName: "main", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) }) }) + + createBranch(ctx, c, client, userName, repoName, "main", "feat/sec_branch") + + c.Convey("list branch", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.ListBranches(ctx, userName, repoName) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("success list branch", func() { + resp, err := client.ListBranches(ctx, userName, repoName) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + respResult, err := api.ParseListBranchesResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(respResult.JSON200.Results, convey.ShouldHaveLength, 3) + }) + + c.Convey("fail to list ref from non exit user", func() { + resp, err := client.ListBranches(ctx, "mock_owner", repoName) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to get non exit branch", func() { + resp, err := client.ListBranches(ctx, userName, "mockrepo") + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to others ref", func() { + resp, err := client.ListBranches(ctx, "jimmy", "happygo") + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + }) + + c.Convey("delete branch", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.DeleteBranch(ctx, userName, repoName, &api.DeleteBranchParams{RefName: "feat/sec_branch"}) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + c.Convey("delete branch in not exit repo", func() { + resp, err := client.DeleteBranch(ctx, userName, repoName, &api.DeleteBranchParams{RefName: "feat/third_branch"}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("delete branch in non exit user", func() { + resp, err := client.DeleteBranch(ctx, "telo", repoName, &api.DeleteBranchParams{RefName: "feat/sec_branch"}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("delete branch in other's repo", func() { + resp, err := client.DeleteBranch(ctx, "jimmy", "happygo", &api.DeleteBranchParams{RefName: "main"}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + + c.Convey("delete branch successful", func() { + resp, err := client.DeleteBranch(ctx, userName, repoName, &api.DeleteBranchParams{RefName: "feat/sec_branch"}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + getResp, err := client.GetBranch(ctx, userName, repoName, &api.GetBranchParams{RefName: "feat/sec_branch"}) + convey.So(err, convey.ShouldBeNil) + convey.So(getResp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + }) + } } + +func createBranch(ctx context.Context, c convey.C, client *api.Client, user string, repoName string, source, refName string) { + c.Convey("create branch "+refName, func() { + resp, err := client.CreateBranch(ctx, user, repoName, api.CreateBranchJSONRequestBody{ + Source: source, + Name: refName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + }) +} diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index cd997204..10ec4f40 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -19,6 +19,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { client, _ := api.NewClient(urlStr + apiimpl.APIV1Prefix) return func(c convey.C) { userName := "jimmy" + repoName := "happyrun" createUser(ctx, c, client, userName) loginAndSwitch(ctx, c, client, userName) @@ -35,7 +36,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("success create repo name", func() { resp, err := client.CreateRepository(ctx, api.CreateRepository{ Description: utils.String("test resp"), - Name: "happyrun", + Name: repoName, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) @@ -66,7 +67,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("duplicate repo name", func() { resp, err := client.CreateRepository(ctx, api.CreateRepository{ Description: utils.String("test resp"), - Name: "happyrun", + Name: repoName, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusInternalServerError) @@ -159,20 +160,20 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors client.RequestEditors = nil - resp, err := client.GetRepository(ctx, userName, "happyrun") + resp, err := client.GetRepository(ctx, userName, repoName) client.RequestEditors = re convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("get repository", func() { - resp, err := client.GetRepository(ctx, userName, "happyrun") + resp, err := client.GetRepository(ctx, userName, repoName) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) getResult, err := api.ParseGetRepositoryResponse(resp) convey.So(err, convey.ShouldBeNil) - convey.So(getResult.JSON200.Name, convey.ShouldEqual, "happyrun") + convey.So(getResult.JSON200.Name, convey.ShouldEqual, repoName) }) c.Convey("get not exit repo", func() { @@ -182,13 +183,13 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("get from non exit user", func() { - resp, err := client.GetRepository(ctx, "telo", "happyrun") + resp, err := client.GetRepository(ctx, "telo", repoName) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) }) c.Convey("get other's repo", func() { - resp, err := client.GetRepository(ctx, "admin", "happyrun") + resp, err := client.GetRepository(ctx, "admin", repoName) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) }) @@ -198,7 +199,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors client.RequestEditors = nil - resp, err := client.UpdateRepository(ctx, userName, "happyrun", api.UpdateRepositoryJSONRequestBody{ + resp, err := client.UpdateRepository(ctx, userName, repoName, api.UpdateRepositoryJSONRequestBody{ Description: utils.String(""), }) client.RequestEditors = re @@ -208,13 +209,13 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("success update repository", func() { description := "mock description" - resp, err := client.UpdateRepository(ctx, userName, "happyrun", api.UpdateRepositoryJSONRequestBody{ + resp, err := client.UpdateRepository(ctx, userName, repoName, api.UpdateRepositoryJSONRequestBody{ Description: utils.String(description), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) - getResp, err := client.GetRepository(ctx, userName, "happyrun") + getResp, err := client.GetRepository(ctx, userName, repoName) convey.So(err, convey.ShouldBeNil) convey.So(getResp.StatusCode, convey.ShouldEqual, http.StatusOK) @@ -234,7 +235,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("update repository in non exit user", func() { description := "" - resp, err := client.UpdateRepository(ctx, "telo", "happyrun", api.UpdateRepositoryJSONRequestBody{ + resp, err := client.UpdateRepository(ctx, "telo", repoName, api.UpdateRepositoryJSONRequestBody{ Description: utils.String(description), }) convey.So(err, convey.ShouldBeNil) @@ -243,7 +244,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("update repository in other's repo", func() { description := "" - resp, err := client.UpdateRepository(ctx, "admin", "happyrun", api.UpdateRepositoryJSONRequestBody{ + resp, err := client.UpdateRepository(ctx, "admin", repoName, api.UpdateRepositoryJSONRequestBody{ Description: utils.String(description), }) convey.So(err, convey.ShouldBeNil) @@ -255,7 +256,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors client.RequestEditors = nil - resp, err := client.GetCommitsInRepository(ctx, userName, "happyrun", &api.GetCommitsInRepositoryParams{ + resp, err := client.GetCommitsInRepository(ctx, userName, repoName, &api.GetCommitsInRepositoryParams{ RefName: utils.String(controller.DefaultBranchName), }) client.RequestEditors = re @@ -264,7 +265,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("success get commits", func() { - resp, err := client.GetCommitsInRepository(ctx, userName, "happyrun", &api.GetCommitsInRepositoryParams{ + resp, err := client.GetCommitsInRepository(ctx, userName, repoName, &api.GetCommitsInRepositoryParams{ RefName: utils.String(controller.DefaultBranchName), }) convey.So(err, convey.ShouldBeNil) @@ -285,7 +286,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("update repository in non exit user", func() { - resp, err := client.GetCommitsInRepository(ctx, "telo", "happyrun", &api.GetCommitsInRepositoryParams{ + resp, err := client.GetCommitsInRepository(ctx, "telo", repoName, &api.GetCommitsInRepositoryParams{ RefName: utils.String(controller.DefaultBranchName), }) convey.So(err, convey.ShouldBeNil) @@ -293,7 +294,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("update repository in other's repo", func() { - resp, err := client.GetCommitsInRepository(ctx, "admin", "happyrun", &api.GetCommitsInRepositoryParams{ + resp, err := client.GetCommitsInRepository(ctx, "admin", repoName, &api.GetCommitsInRepositoryParams{ RefName: utils.String(controller.DefaultBranchName), }) convey.So(err, convey.ShouldBeNil) @@ -305,7 +306,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors client.RequestEditors = nil - resp, err := client.DeleteRepository(ctx, userName, "happyrun") + resp, err := client.DeleteRepository(ctx, userName, repoName) client.RequestEditors = re convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) @@ -317,23 +318,23 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("delete repository in non exit user", func() { - resp, err := client.DeleteRepository(ctx, "telo", "happyrun") + resp, err := client.DeleteRepository(ctx, "telo", repoName) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) }) c.Convey("delete repository in other's repo", func() { - resp, err := client.DeleteRepository(ctx, "admin", "happyrun") + resp, err := client.DeleteRepository(ctx, "admin", repoName) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) }) c.Convey("delete repository successful", func() { - resp, err := client.DeleteRepository(ctx, userName, "happyrun") + resp, err := client.DeleteRepository(ctx, userName, repoName) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) - getResp, err := client.GetRepository(ctx, userName, "happyrun") + getResp, err := client.GetRepository(ctx, userName, repoName) convey.So(err, convey.ShouldBeNil) convey.So(getResp.StatusCode, convey.ShouldEqual, http.StatusNotFound) }) diff --git a/models/commit.go b/models/commit.go index 4e2e5ff9..7ccc0fe0 100644 --- a/models/commit.go +++ b/models/commit.go @@ -120,7 +120,11 @@ func NewCommitRepo(db bun.IDB) ICommitRepo { func (cr CommitRepo) Commit(ctx context.Context, hash hash.Hash) (*Commit, error) { commit := &Commit{} - return commit, cr.db.NewSelect().Model(commit).Where("hash = ?", hash).Scan(ctx) + err := cr.db.NewSelect().Model(commit).Where("hash = ?", hash).Scan(ctx) + if err != nil { + return nil, err + } + return commit, nil } func (cr CommitRepo) Insert(ctx context.Context, commit *Commit) (*Commit, error) { diff --git a/models/ref.go b/models/ref.go index 99aacd30..1171c0b9 100644 --- a/models/ref.go +++ b/models/ref.go @@ -147,7 +147,11 @@ func (r RefRepo) Get(ctx context.Context, params *GetRefParams) (*Ref, error) { query = query.Where("name = ?", *params.Name) } - return repo, query.Limit(1).Scan(ctx) + err := query.Limit(1).Scan(ctx) + if err != nil { + return nil, err + } + return repo, nil } func (r RefRepo) List(ctx context.Context, params *ListRefParams) ([]*Ref, error) { @@ -158,7 +162,11 @@ func (r RefRepo) List(ctx context.Context, params *ListRefParams) ([]*Ref, error query = query.Where("repository_id = ?", params.RepositoryID) } - return refs, query.Scan(ctx) + err := query.Scan(ctx) + if err != nil { + return nil, err + } + return refs, nil } func (r RefRepo) Delete(ctx context.Context, params *DeleteRefParams) error { diff --git a/models/repository.go b/models/repository.go index cb268125..14660e99 100644 --- a/models/repository.go +++ b/models/repository.go @@ -161,7 +161,11 @@ func (r *RepositoryRepo) Get(ctx context.Context, params *GetRepoParams) (*Repos query = query.Where("name = ?", *params.Name) } - return repo, query.Limit(1).Scan(ctx) + err := query.Limit(1).Scan(ctx) + if err != nil { + return nil, err + } + return repo, nil } func (r *RepositoryRepo) List(ctx context.Context, params *ListRepoParams) ([]*Repository, error) { @@ -189,7 +193,11 @@ func (r *RepositoryRepo) List(ctx context.Context, params *ListRepoParams) ([]*R } } - return repos, query.Scan(ctx) + err := query.Scan(ctx) + if err != nil { + return nil, err + } + return repos, nil } func (r *RepositoryRepo) Delete(ctx context.Context, params *DeleteRepoParams) error { diff --git a/models/tree.go b/models/tree.go index df2a3854..32f83e3a 100644 --- a/models/tree.go +++ b/models/tree.go @@ -290,17 +290,29 @@ func (o FileTreeRepo) Get(ctx context.Context, params *GetObjParams) (*FileTree, query = query.Where("hash = ?", params.Hash) } - return repo, query.Limit(1).Scan(ctx, repo) + err := query.Limit(1).Scan(ctx, repo) + if err != nil { + return nil, err + } + return repo, nil } func (o FileTreeRepo) Blob(ctx context.Context, hash hash.Hash) (*Blob, error) { blob := &Blob{} - return blob, o.db.NewSelect().Model(blob).Limit(1).Where("hash = ?", hash).Scan(ctx) + err := o.db.NewSelect().Model(blob).Limit(1).Where("hash = ?", hash).Scan(ctx) + if err != nil { + return nil, err + } + return blob, nil } func (o FileTreeRepo) TreeNode(ctx context.Context, hash hash.Hash) (*TreeNode, error) { tree := &TreeNode{} - return tree, o.db.NewSelect().Model(tree).Limit(1).Where("hash = ?", hash).Scan(ctx) + err := o.db.NewSelect().Model(tree).Limit(1).Where("hash = ?", hash).Scan(ctx) + if err != nil { + return nil, err + } + return tree, nil } func (o FileTreeRepo) Count(ctx context.Context) (int, error) { @@ -309,5 +321,9 @@ func (o FileTreeRepo) Count(ctx context.Context) (int, error) { func (o FileTreeRepo) List(ctx context.Context) ([]FileTree, error) { var obj []FileTree - return obj, o.db.NewSelect().Model(&obj).Scan(ctx) + err := o.db.NewSelect().Model(&obj).Scan(ctx) + if err != nil { + return nil, err + } + return obj, nil } diff --git a/models/user.go b/models/user.go index daf7d4be..d82f267e 100644 --- a/models/user.go +++ b/models/user.go @@ -86,7 +86,11 @@ func (userRepo *UserRepo) Get(ctx context.Context, params *GetUserParams) (*User query = query.Where("email = ?", *params.Email) } - return user, query.Limit(1).Scan(ctx) + err := query.Limit(1).Scan(ctx) + if err != nil { + return nil, err + } + return user, nil } func (userRepo *UserRepo) Count(ctx context.Context, params *GetUserParams) (int, error) { diff --git a/models/wip.go b/models/wip.go index 4d75c1f3..bed8541d 100644 --- a/models/wip.go +++ b/models/wip.go @@ -172,8 +172,8 @@ func (s *WipRepo) Insert(ctx context.Context, repo *WorkingInProcess) (*WorkingI // Get wip by a group of conditions func (s *WipRepo) Get(ctx context.Context, params *GetWipParams) (*WorkingInProcess, error) { - repo := &WorkingInProcess{} - query := s.db.NewSelect().Model(repo) + wips := &WorkingInProcess{} + query := s.db.NewSelect().Model(wips) if uuid.Nil != params.ID { query = query.Where("id = ?", params.ID) @@ -191,7 +191,11 @@ func (s *WipRepo) Get(ctx context.Context, params *GetWipParams) (*WorkingInProc query = query.Where("ref_id = ?", params.RefID) } - return repo, query.Limit(1).Scan(ctx, repo) + err := query.Limit(1).Scan(ctx) + if err != nil { + return nil, err + } + return wips, nil } func (s *WipRepo) List(ctx context.Context, params *ListWipParams) ([]*WorkingInProcess, error) { @@ -210,7 +214,11 @@ func (s *WipRepo) List(ctx context.Context, params *ListWipParams) ([]*WorkingIn query = query.Where("ref_id = ?", params.RefID) } - return resp, query.Scan(ctx) + err := query.Scan(ctx) + if err != nil { + return nil, err + } + return resp, nil } // Delete remove wip in table by id diff --git a/utils/hash/hash.go b/utils/hash/hash.go index 256e3ead..a0f2276d 100644 --- a/utils/hash/hash.go +++ b/utils/hash/hash.go @@ -12,6 +12,8 @@ import ( var _ json.Marshaler = (*Hash)(nil) var _ json.Unmarshaler = (*Hash)(nil) +var EmptyHash = Hash{} + type Hash []byte func (hash Hash) Hex() string { From 308be11d6813d62e1faf0b24e88bf06e4676aaad Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Tue, 19 Dec 2023 20:04:38 +0800 Subject: [PATCH 092/210] feat: add test for wip --- api/jiaozifs.gen.go | 160 +++++++++------------ api/swagger.yml | 7 - controller/branch_ctl.go | 10 +- controller/repository_ctl.go | 17 ++- controller/wip_ctl.go | 85 ++++++++--- integrationtest/branch_test.go | 17 +-- integrationtest/helper.go | 56 ++++++++ integrationtest/repo_test.go | 10 -- integrationtest/root_test.go | 1 + integrationtest/user_test.go | 31 ---- integrationtest/wip_test.go | 251 +++++++++++++++++++++++++++++++++ models/ref.go | 15 +- models/ref_test.go | 3 +- models/repository.go | 37 ++++- models/repository_test.go | 8 +- models/wip.go | 22 +-- models/wip_test.go | 11 +- versionmgr/commit_test.go | 18 ++- 18 files changed, 547 insertions(+), 212 deletions(-) create mode 100644 integrationtest/wip_test.go diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 88a49af6..f865eec6 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -361,9 +361,6 @@ type GetWipParams struct { // CreateWipParams defines parameters for CreateWip. type CreateWipParams struct { - // Name wip name - Name string `form:"name" json:"name"` - // RefName ref name RefName string `form:"refName" json:"refName"` } @@ -2364,18 +2361,6 @@ func NewCreateWipRequest(server string, owner string, repository string, params if params != nil { queryValues := queryURL.Query() - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "name", runtime.ParamLocationQuery, params.Name); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { @@ -5875,21 +5860,6 @@ func (siw *ServerInterfaceWrapper) CreateWip(w http.ResponseWriter, r *http.Requ // Parameter object where we will unmarshal all parameters from the context var params CreateWipParams - // ------------- Required query parameter "name" ------------- - - if paramValue := r.URL.Query().Get("name"); paramValue != "" { - - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "name"}) - return - } - - err = runtime.BindQueryParameter("form", true, true, "name", r.URL.Query(), ¶ms.Name) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "name", Err: err}) - return - } - // ------------- Required query parameter "refName" ------------- if paramValue := r.URL.Query().Get("refName"); paramValue != "" { @@ -6303,71 +6273,71 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 1fa06eff..07a58aaa 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -855,13 +855,6 @@ paths: - wip operationId: createWip summary: create working in process - parameters: - - in: query - name: name - description: wip name - required: true - schema: - type: string responses: 201: description: working in process created diff --git a/controller/branch_ctl.go b/controller/branch_ctl.go index 841adc75..11a4a05a 100644 --- a/controller/branch_ctl.go +++ b/controller/branch_ctl.go @@ -192,16 +192,14 @@ func (bct BranchController) DeleteBranch(ctx context.Context, w *api.JiaozifsRes return } - _, err = bct.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(params.RefName).SetRepositoryID(repository.ID)) + // Delete branch + affectedRows, err := bct.Repo.RefRepo().Delete(ctx, models.NewDeleteRefParams().SetName(params.RefName).SetRepositoryID(repository.ID)) if err != nil { w.Error(err) return } - - // Delete branch - err = bct.Repo.RefRepo().Delete(ctx, models.NewDeleteRefParams().SetName(params.RefName).SetRepositoryID(repository.ID)) - if err != nil { - w.Error(err) + if affectedRows == 0 { + w.NotFound() return } w.OK() diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index a3761adf..7a597c21 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -61,7 +61,7 @@ func (repositoryCtl RepositoryController) ListRepositoryOfAuthenticatedUser(ctx return } - repositories, err := repositoryCtl.Repo.RepositoryRepo().List(ctx, models.NewListRepoParams().SetOwnerID(operator.ID)) + repositories, err := repositoryCtl.Repo.RepositoryRepo().List(ctx, models.NewListRepoParams().SetOwnerID(operator.ID)) //operator is owner if err != nil { w.Error(err) return @@ -131,7 +131,7 @@ func (repositoryCtl RepositoryController) CreateRepository(ctx context.Context, Name: body.Name, Description: body.Description, HEAD: defaultRef.Name, - OwnerID: operator.ID, + OwnerID: operator.ID, // this api only create repo for operator CreatorID: operator.ID, CreatedAt: time.Now(), UpdatedAt: time.Now(), @@ -174,17 +174,22 @@ func (repositoryCtl RepositoryController) DeleteRepository(ctx context.Context, return } - repo, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(operator.ID)) + repo, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) if err != nil { w.Error(err) return } - err = repositoryCtl.Repo.RepositoryRepo().Delete(ctx, models.NewDeleteRepoParams().SetID(repo.ID)) + affectRows, err := repositoryCtl.Repo.RepositoryRepo().Delete(ctx, models.NewDeleteRepoParams().SetID(repo.ID)) if err != nil { w.Error(err) return } + + if affectRows == 0 { + w.NotFound() + return + } w.OK() } @@ -247,7 +252,7 @@ func (repositoryCtl RepositoryController) UpdateRepository(ctx context.Context, } func (repositoryCtl RepositoryController) GetCommitsInRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.GetCommitsInRepositoryParams) { - user, err := auth.GetOperator(ctx) + operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return @@ -259,7 +264,7 @@ func (repositoryCtl RepositoryController) GetCommitsInRepository(ctx context.Con return } - if user.Name != ownerName { //todo check public or private + if operator.Name != ownerName { //todo check public or private w.Forbidden() return } diff --git a/controller/wip_ctl.go b/controller/wip_ctl.go index 21a65460..478ecfd1 100644 --- a/controller/wip_ctl.go +++ b/controller/wip_ctl.go @@ -4,11 +4,14 @@ import ( "bytes" "context" "encoding/hex" + "errors" "fmt" "net/http" "strings" "time" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/jiaozifs/jiaozifs/auth" "github.com/jiaozifs/jiaozifs/versionmgr" @@ -27,7 +30,7 @@ type WipController struct { // CreateWip create wip of branch func (wipCtl WipController) CreateWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.CreateWipParams) { - operatorUser, err := auth.GetOperator(ctx) + operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return @@ -45,7 +48,10 @@ func (wipCtl WipController) CreateWip(ctx context.Context, w *api.JiaozifsRespon return } - //todo check permission to operator ownerRepo + if operator.Name != owner.Name { //todo check permission to operator ownerRepo + w.Forbidden() + return + } ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetRepositoryID(repository.ID).SetName(params.RefName)) if err != nil { @@ -53,20 +59,33 @@ func (wipCtl WipController) CreateWip(ctx context.Context, w *api.JiaozifsRespon return } - baseCommit, err := wipCtl.Repo.CommitRepo().Commit(ctx, ref.CommitHash) - if err != nil { + _, err = wipCtl.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetCreatorID(operator.ID).SetRepositoryID(repository.ID).SetRefID(ref.ID)) + if err == nil { + w.BadRequest(fmt.Sprintf("ref %s already in wip", params.RefName)) + return + } + if err != nil && !errors.Is(err, models.ErrNotFound) { w.Error(err) return } + currentTreeHash := hash.EmptyHash + if !ref.CommitHash.IsEmpty() { + baseCommit, err := wipCtl.Repo.CommitRepo().Commit(ctx, ref.CommitHash) + if err != nil { + w.Error(err) + return + } + currentTreeHash = baseCommit.TreeHash + } + wip := &models.WorkingInProcess{ - CurrentTree: baseCommit.TreeHash, + CurrentTree: currentTreeHash, BaseCommit: ref.CommitHash, RepositoryID: repository.ID, RefID: ref.ID, State: 0, - Name: params.Name, - CreatorID: operatorUser.ID, + CreatorID: operator.ID, CreatedAt: time.Now(), UpdatedAt: time.Now(), } @@ -80,7 +99,7 @@ func (wipCtl WipController) CreateWip(ctx context.Context, w *api.JiaozifsRespon // GetWip get wip of specific repository, operator only get himself wip func (wipCtl WipController) GetWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.GetWipParams) { - user, err := auth.GetOperator(ctx) + operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return @@ -97,14 +116,19 @@ func (wipCtl WipController) GetWip(ctx context.Context, w *api.JiaozifsResponse, w.Error(err) return } - //todo check permission to operator ownerRepo + + if operator.Name != owner.Name { //todo check permission to operator ownerRepo + w.Forbidden() + return + } + ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetRepositoryID(repository.ID).SetName(params.RefName)) if err != nil { w.Error(err) return } - wip, err := wipCtl.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetRefID(ref.ID).SetCreatorID(user.ID).SetRepositoryID(repository.ID)) + wip, err := wipCtl.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetRefID(ref.ID).SetCreatorID(operator.ID).SetRepositoryID(repository.ID)) if err != nil { w.Error(err) return @@ -115,7 +139,7 @@ func (wipCtl WipController) GetWip(ctx context.Context, w *api.JiaozifsResponse, // ListWip return wips of branches, operator only see himself wips in specific repository func (wipCtl WipController) ListWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string) { - user, err := auth.GetOperator(ctx) + operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return @@ -133,7 +157,12 @@ func (wipCtl WipController) ListWip(ctx context.Context, w *api.JiaozifsResponse return } - wips, err := wipCtl.Repo.WipRepo().List(ctx, models.NewListWipParams().SetCreatorID(user.ID).SetRepositoryID(repository.ID)) + if operator.Name != owner.Name { //todo check permission to operator ownerRepo + w.Forbidden() + return + } + + wips, err := wipCtl.Repo.WipRepo().List(ctx, models.NewListWipParams().SetCreatorID(operator.ID).SetRepositoryID(repository.ID)) if err != nil { w.Error(err) return @@ -144,7 +173,7 @@ func (wipCtl WipController) ListWip(ctx context.Context, w *api.JiaozifsResponse // CommitWip commit wip to branch, operator only could operator himself wip func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName, repositoryName string, params api.CommitWipParams) { - operatorUser, err := auth.GetOperator(ctx) + operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return @@ -162,6 +191,11 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon return } + if operator.Name != owner.Name { //todo check permission to operator ownerRepo + w.Forbidden() + return + } + ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(params.RefName)) if err != nil { w.Error(err) @@ -174,7 +208,7 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon return } - wip, err := wipCtl.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetRefID(ref.ID).SetCreatorID(operatorUser.ID).SetRepositoryID(repository.ID)) + wip, err := wipCtl.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetRefID(ref.ID).SetCreatorID(operator.ID).SetRepositoryID(repository.ID)) if err != nil { w.Error(err) return @@ -192,7 +226,7 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon //add commit err = wipCtl.Repo.Transaction(ctx, func(repo models.IRepo) error { commitOp := versionmgr.NewCommitOp(repo, commit) - commit, err := commitOp.AddCommit(ctx, operatorUser, wip.ID, msg) + commit, err := commitOp.AddCommit(ctx, operator, wip.ID, msg) if err != nil { return err } @@ -233,6 +267,11 @@ func (wipCtl WipController) DeleteWip(ctx context.Context, w *api.JiaozifsRespon return } + if operator.Name != owner.Name { //todo check permission to operator ownerRepo + w.Forbidden() + return + } + ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetRepositoryID(repository.ID).SetName(params.RefName)) if err != nil { w.Error(err) @@ -244,18 +283,23 @@ func (wipCtl WipController) DeleteWip(ctx context.Context, w *api.JiaozifsRespon SetRepositoryID(repository.ID). SetRefID(ref.ID) - err = wipCtl.Repo.WipRepo().Delete(ctx, deleteWipParams) + affectedRaw, err := wipCtl.Repo.WipRepo().Delete(ctx, deleteWipParams) if err != nil { w.Error(err) return } + if affectedRaw == 0 { + w.NotFound() + return + } + w.OK() } // GetWipChanges return wip difference, operator only see himself wip func (wipCtl WipController) GetWipChanges(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName, repositoryName string, params api.GetWipChangesParams) { - user, err := auth.GetOperator(ctx) + operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return @@ -273,13 +317,18 @@ func (wipCtl WipController) GetWipChanges(ctx context.Context, w *api.JiaozifsRe return } + if operator.Name != owner.Name { //todo check permission to operator ownerRepo + w.Forbidden() + return + } + ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetRepositoryID(repository.ID).SetName(params.RefName)) if err != nil { w.Error(err) return } - wip, err := wipCtl.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetCreatorID(user.ID).SetRepositoryID(repository.ID).SetRefID(ref.ID)) + wip, err := wipCtl.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetCreatorID(operator.ID).SetRepositoryID(repository.ID).SetRefID(ref.ID)) if err != nil { w.Error(err) return diff --git a/integrationtest/branch_test.go b/integrationtest/branch_test.go index 6e28f3ee..fde6224d 100644 --- a/integrationtest/branch_test.go +++ b/integrationtest/branch_test.go @@ -167,19 +167,19 @@ func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(respResult.JSON200.Results, convey.ShouldHaveLength, 3) }) - c.Convey("fail to list ref from non exit user", func() { + c.Convey("fail to list branchs from non exit user", func() { resp, err := client.ListBranches(ctx, "mock_owner", repoName) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) }) - c.Convey("fail to get non exit branch", func() { + c.Convey("fail to list branchs in non exit repo", func() { resp, err := client.ListBranches(ctx, userName, "mockrepo") convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) }) - c.Convey("fail to others ref", func() { + c.Convey("fail to list branches in others repo", func() { resp, err := client.ListBranches(ctx, "jimmy", "happygo") convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) @@ -226,14 +226,3 @@ func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { } } - -func createBranch(ctx context.Context, c convey.C, client *api.Client, user string, repoName string, source, refName string) { - c.Convey("create branch "+refName, func() { - resp, err := client.CreateBranch(ctx, user, repoName, api.CreateBranchJSONRequestBody{ - Source: source, - Name: refName, - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) - }) -} diff --git a/integrationtest/helper.go b/integrationtest/helper.go index b9eedd26..e12adba9 100644 --- a/integrationtest/helper.go +++ b/integrationtest/helper.go @@ -6,11 +6,15 @@ import ( "errors" "fmt" "io" + "net/http" "os" "strings" "testing" "time" + "github.com/jiaozifs/jiaozifs/api" + "github.com/smartystreets/goconvey/convey" + "github.com/jiaozifs/jiaozifs/testhelper" "github.com/stretchr/testify/require" @@ -85,3 +89,55 @@ func SetupDaemon(t *testing.T, ctx context.Context) (string, Closer) { //nolint } } } + +func createUser(ctx context.Context, c convey.C, client *api.Client, userName string) { + c.Convey("register "+userName, func() { + resp, err := client.Register(ctx, api.RegisterJSONRequestBody{ + Username: userName, + Password: "12345678", + Email: "mock@gmail.com", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + }) +} + +func loginAndSwitch(ctx context.Context, c convey.C, client *api.Client, userName string) { + c.Convey("login "+userName, func() { + resp, err := client.Login(ctx, api.LoginJSONRequestBody{ + Username: userName, + Password: "12345678", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + client.RequestEditors = nil + client.RequestEditors = append(client.RequestEditors, func(ctx context.Context, req *http.Request) error { + for _, cookie := range resp.Cookies() { + req.AddCookie(cookie) + } + return nil + }) + }) +} + +func createBranch(ctx context.Context, c convey.C, client *api.Client, user string, repoName string, source, refName string) { + c.Convey("create branch "+refName, func() { + resp, err := client.CreateBranch(ctx, user, repoName, api.CreateBranchJSONRequestBody{ + Source: source, + Name: refName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + }) +} + +func createRepo(ctx context.Context, c convey.C, client *api.Client, repoName string) { + c.Convey("create repo "+repoName, func() { + resp, err := client.CreateRepository(ctx, api.CreateRepositoryJSONRequestBody{ + Name: repoName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + }) +} diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index 10ec4f40..be93b97e 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -341,13 +341,3 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { }) } } - -func createRepo(ctx context.Context, c convey.C, client *api.Client, repoName string) { - c.Convey("create repo "+repoName, func() { - resp, err := client.CreateRepository(ctx, api.CreateRepositoryJSONRequestBody{ - Name: repoName, - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) - }) -} diff --git a/integrationtest/root_test.go b/integrationtest/root_test.go index 5219617c..129417e8 100644 --- a/integrationtest/root_test.go +++ b/integrationtest/root_test.go @@ -15,4 +15,5 @@ func TestSpec(t *testing.T) { convey.Convey("user test", t, UserSpec(ctx, urlStr)) convey.Convey("repo test", t, RepoSpec(ctx, urlStr)) convey.Convey("branch test", t, BranchSpec(ctx, urlStr)) + convey.Convey("branch test", t, WipSpec(ctx, urlStr)) } diff --git a/integrationtest/user_test.go b/integrationtest/user_test.go index f51b268d..cb284655 100644 --- a/integrationtest/user_test.go +++ b/integrationtest/user_test.go @@ -40,34 +40,3 @@ func UserSpec(ctx context.Context, urlStr string) func(c convey.C) { }) } } - -func createUser(ctx context.Context, c convey.C, client *api.Client, userName string) { - c.Convey("register "+userName, func() { - resp, err := client.Register(ctx, api.RegisterJSONRequestBody{ - Username: userName, - Password: "12345678", - Email: "mock@gmail.com", - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) - }) -} - -func loginAndSwitch(ctx context.Context, c convey.C, client *api.Client, userName string) { - c.Convey("login "+userName, func() { - resp, err := client.Login(ctx, api.LoginJSONRequestBody{ - Username: userName, - Password: "12345678", - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) - - client.RequestEditors = nil - client.RequestEditors = append(client.RequestEditors, func(ctx context.Context, req *http.Request) error { - for _, cookie := range resp.Cookies() { - req.AddCookie(cookie) - } - return nil - }) - }) -} diff --git a/integrationtest/wip_test.go b/integrationtest/wip_test.go new file mode 100644 index 00000000..2c5ea665 --- /dev/null +++ b/integrationtest/wip_test.go @@ -0,0 +1,251 @@ +package integrationtest + +import ( + "context" + "net/http" + + "github.com/jiaozifs/jiaozifs/api" + apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" + "github.com/smartystreets/goconvey/convey" +) + +func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { + client, _ := api.NewClient(urlStr + apiimpl.APIV1Prefix) + return func(c convey.C) { + userName := "july" + repoName := "mlops" + refName := "feat/wip_test" + refNameForDelete := "feat/wip_test2" + + createUser(ctx, c, client, userName) + loginAndSwitch(ctx, c, client, userName) + createRepo(ctx, c, client, repoName) + createBranch(ctx, c, client, userName, repoName, "main", refName) + createBranch(ctx, c, client, userName, repoName, "main", refNameForDelete) + c.Convey("list non exit wip", func(c convey.C) { + resp, err := client.ListWip(ctx, userName, repoName) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + respResult, err := api.ParseListWipResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(respResult.JSON200, convey.ShouldHaveLength, 0) + }) + + c.Convey("create wip", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.CreateWip(ctx, userName, repoName, &api.CreateWipParams{ + RefName: refName, + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("fail to create branch in non exit repo", func() { + resp, err := client.CreateWip(ctx, userName, "fakerepo", &api.CreateWipParams{ + RefName: refName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to create branch in non exit user", func() { + resp, err := client.CreateWip(ctx, "mock_user", "main", &api.CreateWipParams{ + RefName: refName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to create branch in non exit ref", func() { + resp, err := client.CreateWip(ctx, userName, repoName, &api.CreateWipParams{ + RefName: "mock ref", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("forbidden create branch in others", func() { + resp, err := client.CreateWip(ctx, "jimmy", "happygo", &api.CreateWipParams{ + RefName: "main", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + + c.Convey("success create branch", func() { + resp, err := client.CreateWip(ctx, userName, repoName, &api.CreateWipParams{ + RefName: refName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + }) + + c.Convey("user only have one wip for one userName", func() { + resp, err := client.CreateWip(ctx, userName, repoName, &api.CreateWipParams{ + RefName: refName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + }) + + createWip(ctx, c, client, userName, repoName, "main") + + c.Convey("list wip", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.ListWip(ctx, userName, repoName) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("success list wips", func() { + resp, err := client.ListWip(ctx, userName, repoName) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + respResult, err := api.ParseListWipResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(respResult.JSON200, convey.ShouldHaveLength, 2) + }) + + c.Convey("fail to list wip from non exit user", func() { + resp, err := client.ListWip(ctx, "mock_owner", repoName) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to list wips in non exit branch", func() { + resp, err := client.ListWip(ctx, userName, "mockrepo") + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to list wip in others's repo", func() { + resp, err := client.ListWip(ctx, "jimmy", "happygo") + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + }) + + c.Convey("get wip", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.GetWip(ctx, userName, repoName, &api.GetWipParams{ + RefName: refName, + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("success get branch", func() { + resp, err := client.GetWip(ctx, userName, repoName, &api.GetWipParams{ + RefName: refName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + _, err = api.ParseGetWipResponse(resp) + convey.So(err, convey.ShouldBeNil) + }) + + c.Convey("fail to get wip in non exit ref", func() { + resp, err := client.GetWip(ctx, userName, repoName, &api.GetWipParams{ + RefName: "mock_ref", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to get wip from non exit user", func() { + resp, err := client.GetWip(ctx, "mock_owner", repoName, &api.GetWipParams{ + RefName: refName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to get non exit branch", func() { + resp, err := client.GetWip(ctx, userName, "mock_repo", &api.GetWipParams{ + RefName: refName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to others repo's wips", func() { + resp, err := client.GetWip(ctx, "jimmy", "happygo", &api.GetWipParams{ + RefName: "main", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + }) + + c.Convey("delete wip", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.DeleteWip(ctx, userName, repoName, &api.DeleteWipParams{RefName: refNameForDelete}) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("delete non exit wip", func() { + resp, err := client.DeleteWip(ctx, userName, repoName, &api.DeleteWipParams{RefName: refNameForDelete}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("delete wip in not exit repo", func() { + resp, err := client.DeleteWip(ctx, userName, "mock_repo", &api.DeleteWipParams{RefName: refNameForDelete}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("delete wip in non exit user", func() { + resp, err := client.DeleteWip(ctx, "telo", repoName, &api.DeleteWipParams{RefName: refNameForDelete}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("delete wip in other's repo", func() { + resp, err := client.DeleteWip(ctx, "jimmy", "happygo", &api.DeleteWipParams{RefName: "main"}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + + createWip(ctx, c, client, userName, repoName, refNameForDelete) + c.Convey("delete branch successful", func() { + //delete + resp, err := client.DeleteWip(ctx, userName, repoName, &api.DeleteWipParams{RefName: refNameForDelete}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + //ensure delete work + getResp, err := client.GetWip(ctx, userName, repoName, &api.GetWipParams{RefName: refNameForDelete}) + convey.So(err, convey.ShouldBeNil) + convey.So(getResp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + }) + } +} + +func createWip(ctx context.Context, c convey.C, client *api.Client, user string, repoName string, refName string) { + c.Convey("create branch "+refName, func() { + resp, err := client.CreateWip(ctx, user, repoName, &api.CreateWipParams{ + RefName: refName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + }) +} diff --git a/models/ref.go b/models/ref.go index 1171c0b9..42ee1202 100644 --- a/models/ref.go +++ b/models/ref.go @@ -110,7 +110,7 @@ type IRefRepo interface { Get(ctx context.Context, id *GetRefParams) (*Ref, error) List(ctx context.Context, params *ListRefParams) ([]*Ref, error) - Delete(ctx context.Context, params *DeleteRefParams) error + Delete(ctx context.Context, params *DeleteRefParams) (int64, error) } var _ IRefRepo = (*RefRepo)(nil) @@ -169,7 +169,7 @@ func (r RefRepo) List(ctx context.Context, params *ListRefParams) ([]*Ref, error return refs, nil } -func (r RefRepo) Delete(ctx context.Context, params *DeleteRefParams) error { +func (r RefRepo) Delete(ctx context.Context, params *DeleteRefParams) (int64, error) { query := r.db.NewDelete().Model((*Ref)(nil)) if uuid.Nil != params.ID { @@ -184,8 +184,15 @@ func (r RefRepo) Delete(ctx context.Context, params *DeleteRefParams) error { query = query.Where("name = ?", *params.Name) } - _, err := query.Exec(ctx) - return err + sqlResult, err := query.Exec(ctx) + if err != nil { + return 0, err + } + affectedRows, err := sqlResult.RowsAffected() + if err != nil { + return 0, err + } + return affectedRows, err } func (r RefRepo) UpdateByID(ctx context.Context, updateModel *UpdateRefParams) error { diff --git a/models/ref_test.go b/models/ref_test.go index b5255989..8f45c792 100644 --- a/models/ref_test.go +++ b/models/ref_test.go @@ -50,8 +50,9 @@ func TestRefRepoInsert(t *testing.T) { require.NoError(t, err) require.Len(t, list, 1) - err = repo.Delete(ctx, models.NewDeleteRefParams().SetID(list[0].ID).SetRepositoryID(list[0].RepositoryID).SetName(list[0].Name)) + affectedRows, err := repo.Delete(ctx, models.NewDeleteRefParams().SetID(list[0].ID).SetRepositoryID(list[0].RepositoryID).SetName(list[0].Name)) require.NoError(t, err) + require.Equal(t, int64(1), affectedRows) list, err = repo.List(ctx, models.NewListRefParams().SetRepositoryID(ref.RepositoryID)) require.NoError(t, err) diff --git a/models/repository.go b/models/repository.go index 14660e99..fa45739a 100644 --- a/models/repository.go +++ b/models/repository.go @@ -85,7 +85,9 @@ func (lrp *ListRepoParams) SetCreatorID(creatorID uuid.UUID) *ListRepoParams { } type DeleteRepoParams struct { - ID uuid.UUID + ID uuid.UUID + OwnerID uuid.UUID + Name *string } func NewDeleteRepoParams() *DeleteRepoParams { @@ -97,6 +99,16 @@ func (drp *DeleteRepoParams) SetID(id uuid.UUID) *DeleteRepoParams { return drp } +func (drp *DeleteRepoParams) SetOwnerID(ownerID uuid.UUID) *DeleteRepoParams { + drp.OwnerID = ownerID + return drp +} + +func (drp *DeleteRepoParams) SetName(name string) *DeleteRepoParams { + drp.Name = &name + return drp +} + type UpdateRepoParams struct { bun.BaseModel `bun:"table:repositories"` ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` @@ -119,7 +131,7 @@ type IRepositoryRepo interface { Get(ctx context.Context, params *GetRepoParams) (*Repository, error) List(ctx context.Context, params *ListRepoParams) ([]*Repository, error) - Delete(ctx context.Context, params *DeleteRepoParams) error + Delete(ctx context.Context, params *DeleteRepoParams) (int64, error) UpdateByID(ctx context.Context, updateModel *UpdateRepoParams) error } @@ -200,14 +212,29 @@ func (r *RepositoryRepo) List(ctx context.Context, params *ListRepoParams) ([]*R return repos, nil } -func (r *RepositoryRepo) Delete(ctx context.Context, params *DeleteRepoParams) error { +func (r *RepositoryRepo) Delete(ctx context.Context, params *DeleteRepoParams) (int64, error) { query := r.db.NewDelete().Model((*Repository)(nil)) if uuid.Nil != params.ID { query = query.Where("id = ?", params.ID) } - _, err := query.Exec(ctx) - return err + if params.Name != nil { + query = query.Where("name = ?", params.Name) + } + + if uuid.Nil != params.OwnerID { + query = query.Where("owner_id = ?", params.OwnerID) + } + + sqlResult, err := query.Exec(ctx) + if err != nil { + return 0, err + } + affectedRows, err := sqlResult.RowsAffected() + if err != nil { + return 0, err + } + return affectedRows, err } func (r *RepositoryRepo) UpdateByID(ctx context.Context, updateModel *UpdateRepoParams) error { diff --git a/models/repository_test.go b/models/repository_test.go index 8cec45b0..bc8b411e 100644 --- a/models/repository_test.go +++ b/models/repository_test.go @@ -82,8 +82,14 @@ func TestRepositoryRepo_Insert(t *testing.T) { require.Len(t, repos, 1) } //delete - err = repo.Delete(ctx, models.NewDeleteRepoParams().SetID(secRepo.ID)) + deleteParams := models.NewDeleteRepoParams(). + SetID(secRepo.ID). + SetOwnerID(secRepo.OwnerID). + SetName(secRepo.Name) + affectRows, err := repo.Delete(ctx, deleteParams) require.NoError(t, err) + require.Equal(t, int64(1), affectRows) + _, err = repo.Get(ctx, models.NewGetRepoParams().SetID(secRepo.ID)) require.ErrorIs(t, err, models.ErrNotFound) } diff --git a/models/wip.go b/models/wip.go index bed8541d..557fbe95 100644 --- a/models/wip.go +++ b/models/wip.go @@ -19,13 +19,12 @@ const ( type WorkingInProcess struct { bun.BaseModel `bun:"table:wips"` ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` - Name string `bun:"name,notnull"` CurrentTree hash.Hash `bun:"current_tree,type:bytea,notnull"` BaseCommit hash.Hash `bun:"base_commit,type:bytea,notnull"` - RepositoryID uuid.UUID `bun:"repository_id,type:uuid,notnull"` - RefID uuid.UUID `bun:"ref_id,type:uuid,notnull"` + RepositoryID uuid.UUID `bun:"repository_id,unique:creator_id_repository_id_ref_id_unique,type:uuid,notnull"` + RefID uuid.UUID `bun:"ref_id,unique:creator_id_repository_id_ref_id_unique,type:uuid,notnull"` State WipState `bun:"state,notnull"` - CreatorID uuid.UUID `bun:"creator_id,type:uuid,notnull"` + CreatorID uuid.UUID `bun:"creator_id,unique:creator_id_repository_id_ref_id_unique,type:uuid,notnull"` CreatedAt time.Time `bun:"created_at"` UpdatedAt time.Time `bun:"updated_at"` } @@ -148,7 +147,7 @@ type IWipRepo interface { Insert(ctx context.Context, repo *WorkingInProcess) (*WorkingInProcess, error) Get(ctx context.Context, params *GetWipParams) (*WorkingInProcess, error) List(ctx context.Context, params *ListWipParams) ([]*WorkingInProcess, error) - Delete(ctx context.Context, params *DeleteWipParams) error + Delete(ctx context.Context, params *DeleteWipParams) (int64, error) UpdateByID(ctx context.Context, params *UpdateWipParams) error } @@ -222,7 +221,7 @@ func (s *WipRepo) List(ctx context.Context, params *ListWipParams) ([]*WorkingIn } // Delete remove wip in table by id -func (s *WipRepo) Delete(ctx context.Context, params *DeleteWipParams) error { +func (s *WipRepo) Delete(ctx context.Context, params *DeleteWipParams) (int64, error) { query := s.db.NewDelete().Model((*WorkingInProcess)(nil)) if uuid.Nil != params.CreatorID { @@ -240,8 +239,15 @@ func (s *WipRepo) Delete(ctx context.Context, params *DeleteWipParams) error { if uuid.Nil != params.ID { query = query.Where("id = ?", params.ID) } - _, err := query.Exec(ctx) - return err + r, err := query.Exec(ctx) + if err != nil { + return 0, err + } + row, err := r.RowsAffected() + if err != nil { + return 0, err + } + return row, nil } func (s *WipRepo) UpdateByID(ctx context.Context, updateModel *UpdateWipParams) error { diff --git a/models/wip_test.go b/models/wip_test.go index a9fbe79e..c49455cc 100644 --- a/models/wip_test.go +++ b/models/wip_test.go @@ -41,15 +41,13 @@ func TestWipRepo(t *testing.T) { require.NoError(t, gofakeit.Struct(secWipModel)) secWipModel.CreatorID = newWipModel.CreatorID secWipModel.RepositoryID = newWipModel.RepositoryID - secWipModel.RefID = newWipModel.RefID secNewWipModel, err := repo.Insert(ctx, secWipModel) require.NoError(t, err) require.NotEqual(t, uuid.Nil, secNewWipModel.ID) listParams := models.NewListWipParams(). SetCreatorID(secNewWipModel.CreatorID). - SetRepositoryID(secNewWipModel.RepositoryID). - SetRefID(secNewWipModel.RefID) + SetRepositoryID(secNewWipModel.RepositoryID) list, err := repo.List(ctx, listParams) require.NoError(t, err) @@ -60,7 +58,12 @@ func TestWipRepo(t *testing.T) { SetCreatorID(secWipModel.CreatorID). SetRepositoryID(secWipModel.RepositoryID). SetRefID(secWipModel.RefID) - err = repo.Delete(ctx, deleteParams) + affectedRow, err := repo.Delete(ctx, deleteParams) + require.Equal(t, int64(1), affectedRow) + require.NoError(t, err) + + affectedRow, err = repo.Delete(ctx, deleteParams) + require.Equal(t, int64(0), affectedRow) require.NoError(t, err) _, err = repo.Get(ctx, models.NewGetWipParams().SetID(secWipModel.ID)) diff --git a/versionmgr/commit_test.go b/versionmgr/commit_test.go index fb6b091c..07d7f8a6 100644 --- a/versionmgr/commit_test.go +++ b/versionmgr/commit_test.go @@ -55,6 +55,7 @@ func TestCommitOpDiffCommit(t *testing.T) { baseCommit, err := NewCommitOp(repo, nil).AddCommit(ctx, user, baseWip.ID, "base commit") require.NoError(t, err) + require.NoError(t, rmWip(ctx, repo.WipRepo(), baseWip.ID)) testData2 := ` 3|a.txt |a1 @@ -69,6 +70,7 @@ func TestCommitOpDiffCommit(t *testing.T) { require.NoError(t, err) secondCommit, err := NewCommitOp(repo, nil).AddCommit(ctx, user, secondWip.ID, "merge commit") require.NoError(t, err) + require.NoError(t, rmWip(ctx, repo.WipRepo(), secondWip.ID)) changes, err := baseCommit.DiffCommit(ctx, secondCommit.Commit().Hash) require.NoError(t, err) @@ -133,7 +135,7 @@ func TestCommitOpMerge(t *testing.T) { oriCommit, err := NewCommitOp(repo, nil).AddCommit(ctx, user, oriWip.ID, "") require.NoError(t, err) - + require.NoError(t, rmWip(ctx, repo.WipRepo(), oriWip.ID)) //modify a.txt //CommitA testData = ` @@ -148,6 +150,7 @@ func TestCommitOpMerge(t *testing.T) { commitA, err := NewCommitOp(repo, oriCommit.Commit()).AddCommit(ctx, user, baseWip.ID, "commit a") require.NoError(t, err) + require.NoError(t, rmWip(ctx, repo.WipRepo(), baseWip.ID)) //toMerge branch mergeRef, err := makeRef(ctx, repo.RefRepo(), "feat/merge", project.ID, hash.Hash("a")) @@ -165,6 +168,7 @@ func TestCommitOpMerge(t *testing.T) { require.NoError(t, err) commitB, err := NewCommitOp(repo, oriCommit.Commit()).AddCommit(ctx, user, mergeWip.ID, "commit b") require.NoError(t, err) + require.NoError(t, rmWip(ctx, repo.WipRepo(), mergeWip.ID)) //CommitAB commitAB, err := commitA.Merge(ctx, user, commitB.Commit().Hash, "commit ab", LeastHashResolve) @@ -180,6 +184,7 @@ func TestCommitOpMerge(t *testing.T) { require.NoError(t, err) commitF, err := NewCommitOp(repo, oriCommit.Commit()).AddCommit(ctx, user, mergeWipF.ID, "commit f") require.NoError(t, err) + require.NoError(t, rmWip(ctx, repo.WipRepo(), mergeWipF.ID)) //commitC commitC, err := commitA.Merge(ctx, user, commitF.Commit().Hash, "commit c", LeastHashResolve) @@ -197,6 +202,7 @@ func TestCommitOpMerge(t *testing.T) { require.NoError(t, err) commitD, err := commitB.AddCommit(ctx, user, mergeWipD.ID, "commit d") require.NoError(t, err) + require.NoError(t, rmWip(ctx, repo.WipRepo(), mergeWipD.ID)) //commitE testData = ` 2|a.txt |h4 @@ -207,7 +213,7 @@ func TestCommitOpMerge(t *testing.T) { require.NoError(t, err) commitE, err := commitD.AddCommit(ctx, user, mergeWipE.ID, "commit e") require.NoError(t, err) - + require.NoError(t, rmWip(ctx, repo.WipRepo(), mergeWipE.ID)) //test fast-ward fastMergeCommit, err := commitB.Merge(ctx, user, commitE.Commit().Hash, "", LeastHashResolve) @@ -261,6 +267,7 @@ func TestCrissCrossMerge(t *testing.T) { oriCommit, err := NewCommitOp(repo, nil).AddCommit(ctx, user, oriWip.ID, "") require.NoError(t, err) + require.NoError(t, rmWip(ctx, repo.WipRepo(), oriWip.ID)) //CommitA testData = ` @@ -275,6 +282,7 @@ func TestCrissCrossMerge(t *testing.T) { commitA, err := NewCommitOp(repo, oriCommit.Commit()).AddCommit(ctx, user, baseWip.ID, "commit a") require.NoError(t, err) + require.NoError(t, rmWip(ctx, repo.WipRepo(), baseWip.ID)) //toMerge branch mergeRef, err := makeRef(ctx, repo.RefRepo(), "feat/merge", project.ID, hash.Hash("a")) @@ -292,6 +300,7 @@ func TestCrissCrossMerge(t *testing.T) { require.NoError(t, err) commitB, err := NewCommitOp(repo, oriCommit.Commit()).AddCommit(ctx, user, mergeWip.ID, "commit b") require.NoError(t, err) + require.NoError(t, rmWip(ctx, repo.WipRepo(), mergeWip.ID)) commitAB, err := commitA.Merge(ctx, user, commitB.Commit().Hash, "commit ab", LeastHashResolve) require.NoError(t, err) @@ -381,6 +390,11 @@ func makeWip(ctx context.Context, wipRepo models.IWipRepo, repoID, refID uuid.UU return wipRepo.Insert(ctx, wip) } +func rmWip(ctx context.Context, wipRepo models.IWipRepo, wipID uuid.UUID) error { + _, err := wipRepo.Delete(ctx, models.NewDeleteWipParams().SetID(wipID)) + return err +} + func makeRoot(ctx context.Context, objRepo models.IFileTreeRepo, treeEntry models.TreeEntry, testData string) (*models.TreeNode, error) { lines := strings.Split(testData, "\n") treeOp, err := NewWorkTree(ctx, objRepo, treeEntry) From c7a6fde12d8425432a1b41c6c97e2b53061370b5 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Tue, 19 Dec 2023 22:42:03 +0800 Subject: [PATCH 093/210] test: add test for object --- api/jiaozifs.gen.go | 228 ++++++------------- api/swagger.yml | 28 --- controller/object_ctl.go | 99 +++++---- controller/wip_ctl.go | 23 +- integrationtest/helper.go | 27 +++ integrationtest/objects_test.go | 377 ++++++++++++++++++++++++++++++++ integrationtest/root_test.go | 1 + integrationtest/wip_test.go | 2 +- models/tree.go | 1 + utils/hash/hashing_reader.go | 14 +- 10 files changed, 556 insertions(+), 244 deletions(-) create mode 100644 integrationtest/objects_test.go diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index f865eec6..00be6513 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -255,9 +255,6 @@ type LoginJSONBody struct { // DeleteObjectParams defines parameters for DeleteObject. type DeleteObjectParams struct { - // WipID working in process - WipID openapi_types.UUID `form:"wipID" json:"wipID"` - // Branch branch to the ref Branch string `form:"branch" json:"branch"` @@ -297,18 +294,11 @@ type UploadObjectMultipartBody struct { // UploadObjectParams defines parameters for UploadObject. type UploadObjectParams struct { - // WipID working in process - WipID openapi_types.UUID `form:"wipID" json:"wipID"` - // Branch branch to the ref Branch string `form:"branch" json:"branch"` // Path relative to the ref Path string `form:"path" json:"path"` - - // IfNoneMatch Currently supports only "*" to allow uploading an object only if one doesn't exist yet. - // Deprecated, this capability will not be supported in future releases. - IfNoneMatch *string `json:"If-None-Match,omitempty"` } // DeleteBranchParams defines parameters for DeleteBranch. @@ -1083,18 +1073,6 @@ func NewDeleteObjectRequest(server string, owner string, repository string, para if params != nil { queryValues := queryURL.Query() - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "wipID", runtime.ParamLocationQuery, params.WipID); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "branch", runtime.ParamLocationQuery, params.Branch); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { @@ -1338,18 +1316,6 @@ func NewUploadObjectRequestWithBody(server string, owner string, repository stri if params != nil { queryValues := queryURL.Query() - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "wipID", runtime.ParamLocationQuery, params.WipID); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "branch", runtime.ParamLocationQuery, params.Branch); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { @@ -1384,21 +1350,6 @@ func NewUploadObjectRequestWithBody(server string, owner string, repository stri req.Header.Add("Content-Type", contentType) - if params != nil { - - if params.IfNoneMatch != nil { - var headerParam0 string - - headerParam0, err = runtime.StyleParamWithLocation("simple", false, "If-None-Match", runtime.ParamLocationHeader, *params.IfNoneMatch) - if err != nil { - return nil, err - } - - req.Header.Set("If-None-Match", headerParam0) - } - - } - return req, nil } @@ -4666,21 +4617,6 @@ func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.R // Parameter object where we will unmarshal all parameters from the context var params DeleteObjectParams - // ------------- Required query parameter "wipID" ------------- - - if paramValue := r.URL.Query().Get("wipID"); paramValue != "" { - - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "wipID"}) - return - } - - err = runtime.BindQueryParameter("form", true, true, "wipID", r.URL.Query(), ¶ms.WipID) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "wipID", Err: err}) - return - } - // ------------- Required query parameter "branch" ------------- if paramValue := r.URL.Query().Get("branch"); paramValue != "" { @@ -4945,21 +4881,6 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R // Parameter object where we will unmarshal all parameters from the context var params UploadObjectParams - // ------------- Required query parameter "wipID" ------------- - - if paramValue := r.URL.Query().Get("wipID"); paramValue != "" { - - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "wipID"}) - return - } - - err = runtime.BindQueryParameter("form", true, true, "wipID", r.URL.Query(), ¶ms.WipID) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "wipID", Err: err}) - return - } - // ------------- Required query parameter "branch" ------------- if paramValue := r.URL.Query().Get("branch"); paramValue != "" { @@ -4990,27 +4911,6 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R return } - headers := r.Header - - // ------------- Optional header parameter "If-None-Match" ------------- - if valueList, found := headers[http.CanonicalHeaderKey("If-None-Match")]; found { - var IfNoneMatch string - n := len(valueList) - if n != 1 { - siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "If-None-Match", Count: n}) - return - } - - err = runtime.BindStyledParameterWithOptions("simple", "If-None-Match", valueList[0], &IfNoneMatch, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "If-None-Match", Err: err}) - return - } - - params.IfNoneMatch = &IfNoneMatch - - } - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.UploadObject(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) @@ -6273,71 +6173,69 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "XWhxo2NcAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 07a58aaa..96f969e9 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -736,26 +736,6 @@ paths: schema: type: string format: binary - - parameters: - - in: query - name: wipID - description: working in process - required: true - schema: - type: string - format: uuid - - in: header - name: If-None-Match - description: | - Currently supports only "*" to allow uploading an object only if one doesn't exist yet. - Deprecated, this capability will not be supported in future releases. - example: "*" - required: false - deprecated: true - schema: - type: string - pattern: '^\*$' # Currently, only "*" is supported responses: 201: description: object metadata @@ -780,14 +760,6 @@ paths: - objects operationId: deleteObject summary: delete object. Missing objects will not return a NotFound error. - parameters: - - in: query - name: wipID - description: working in process - required: true - schema: - type: string - format: uuid responses: 204: description: object deleted successfully diff --git a/controller/object_ctl.go b/controller/object_ctl.go index 5c8d4320..793fccaf 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -10,6 +10,8 @@ import ( "net/http" "time" + "github.com/jiaozifs/jiaozifs/utils/hash" + logging "github.com/ipfs/go-log/v2" "github.com/go-openapi/swag" @@ -39,7 +41,7 @@ type ObjectController struct { } func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, ownerName string, repositoryName string, params api.DeleteObjectParams) { //nolint - user, err := auth.GetOperator(ctx) + operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return @@ -51,7 +53,7 @@ func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsRes return } - if user.Name != ownerName { //todo check permission + if operator.Name != ownerName { //todo check permission w.Forbidden() return } @@ -73,6 +75,12 @@ func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsRes return } + wip, err := oct.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetCreatorID(operator.ID).SetRepositoryID(repository.ID).SetRefID(ref.ID)) + if err != nil { + w.Error(err) + return + } + workTree, err := versionmgr.NewWorkTree(ctx, oct.Repo.FileTreeRepo(), models.NewRootTreeEntry(commit.TreeHash)) if err != nil { w.Error(err) @@ -81,11 +89,11 @@ func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsRes err = workTree.RemoveEntry(ctx, params.Path) if errors.Is(err, versionmgr.ErrPathNotFound) { - w.Code(http.StatusNotFound) + w.BadRequest(fmt.Sprintf("path %s not found", params.Path)) return } - err = oct.Repo.WipRepo().UpdateByID(ctx, models.NewUpdateWipParams(params.WipID).SetCurrentTree(workTree.Root().Hash())) + err = oct.Repo.WipRepo().UpdateByID(ctx, models.NewUpdateWipParams(wip.ID).SetCurrentTree(workTree.Root().Hash())) if err != nil { w.Error(err) return @@ -123,13 +131,17 @@ func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsRespon return } - commit, err := oct.Repo.CommitRepo().Commit(ctx, ref.CommitHash) - if err != nil { - w.Error(err) - return + treeHash := hash.EmptyHash + if !ref.CommitHash.IsEmpty() { + commit, err := oct.Repo.CommitRepo().Commit(ctx, ref.CommitHash) + if err != nil { + w.Error(err) + return + } + treeHash = commit.TreeHash } - workTree, err := versionmgr.NewWorkTree(ctx, oct.Repo.FileTreeRepo(), models.NewRootTreeEntry(commit.TreeHash)) + workTree, err := versionmgr.NewWorkTree(ctx, oct.Repo.FileTreeRepo(), models.NewRootTreeEntry(treeHash)) if err != nil { w.Error(err) return @@ -137,6 +149,10 @@ func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsRespon blob, name, err := workTree.FindBlob(ctx, params.Path) if err != nil { + if errors.Is(err, versionmgr.ErrPathNotFound) { + w.BadRequest(fmt.Sprintf("path %s not found", params.Path)) + return + } w.Error(err) return } @@ -160,7 +176,7 @@ func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsRespon w.Header().Set("Content-Length", fmt.Sprint(blob.Size)) } - etag := httputil.ETag(blob.Hash.Hex()) + etag := httputil.ETag(blob.CheckSum.Hex()) w.Header().Set("ETag", etag) lastModified := httputil.HeaderTimestamp(blob.CreatedAt) w.Header().Set("Last-Modified", lastModified) @@ -212,44 +228,40 @@ func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsRespo return } - commit, err := oct.Repo.CommitRepo().Commit(ctx, ref.CommitHash) - if err != nil { - w.Error(err) - return + treeHash := hash.EmptyHash + if !ref.CommitHash.IsEmpty() { + commit, err := oct.Repo.CommitRepo().Commit(ctx, ref.CommitHash) + if err != nil { + w.Error(err) + return + } + treeHash = commit.TreeHash } fileRepo := oct.Repo.FileTreeRepo() - treeOp, err := versionmgr.NewWorkTree(ctx, fileRepo, models.NewRootTreeEntry(commit.TreeHash)) + workTree, err := versionmgr.NewWorkTree(ctx, fileRepo, models.NewRootTreeEntry(treeHash)) if err != nil { w.Error(err) return } - existNodes, missingPath, err := treeOp.MatchPath(ctx, params.Path) - if err != nil { - w.Error(err) - return - } - if len(missingPath) == 0 { - w.Error(versionmgr.ErrPathNotFound) - return - } - - objectWithName := existNodes[len(existNodes)-1] - - blob, err := fileRepo.Blob(ctx, objectWithName.Node().Hash) + blob, name, err := workTree.FindBlob(ctx, params.Path) if err != nil { + if errors.Is(err, versionmgr.ErrPathNotFound) { + w.BadRequest(fmt.Sprintf("path %s not found", params.Path)) + return + } w.Error(err) return } //lookup files - etag := httputil.ETag(objectWithName.Node().Hash.Hex()) + etag := httputil.ETag(blob.CheckSum.Hex()) w.Header().Set("ETag", etag) - lastModified := httputil.HeaderTimestamp(objectWithName.Node().CreatedAt) + lastModified := httputil.HeaderTimestamp(blob.CreatedAt) w.Header().Set("Last-Modified", lastModified) w.Header().Set("Accept-Ranges", "bytes") - w.Header().Set("Content-Type", httputil.ExtensionsByType(objectWithName.Entry().Name)) + w.Header().Set("Content-Type", httputil.ExtensionsByType(name)) // for security, make sure the browser and any proxies en route don't cache the response w.Header().Set("Cache-Control", "no-store, must-revalidate") w.Header().Set("Expires", "0") @@ -315,7 +327,7 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes } defer reader.Close() //nolint - user, err := auth.GetOperator(ctx) + operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return @@ -327,18 +339,30 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes return } - if user.Name != ownerName { //todo check permission + if operator.Name != ownerName { //todo check permission w.Forbidden() return } - _, err = oct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) + repository, err := oct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) + if err != nil { + w.Error(err) + return + } + + ref, err := oct.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(params.Branch).SetRepositoryID(repository.ID)) + if err != nil { + w.Error(err) + return + } + + wip, err := oct.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetCreatorID(operator.ID).SetRepositoryID(repository.ID).SetRefID(ref.ID)) if err != nil { w.Error(err) return } - stash, err := oct.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetID(params.WipID)) + stash, err := oct.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetID(wip.ID)) if err != nil { w.Error(err) return @@ -351,6 +375,7 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes return err } + // todo move write blob out of transaction blob, err := workingTree.WriteBlob(ctx, oct.BlockAdapter, reader, r.ContentLength, models.DefaultLeafProperty()) if err != nil { return err @@ -361,7 +386,7 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes return err } response = api.ObjectStats{ - Checksum: blob.Hash.Hex(), + Checksum: blob.CheckSum.Hex(), Mtime: time.Now().Unix(), Path: params.Path, PathMode: utils.Uint32(uint32(filemode.Regular)), @@ -377,5 +402,5 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes return } - w.JSON(response) + w.JSON(response, http.StatusCreated) } diff --git a/controller/wip_ctl.go b/controller/wip_ctl.go index 478ecfd1..cb2b5d46 100644 --- a/controller/wip_ctl.go +++ b/controller/wip_ctl.go @@ -202,22 +202,26 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon return } - commit, err := wipCtl.Repo.CommitRepo().Commit(ctx, ref.CommitHash) - if err != nil { - w.Error(err) - return - } - wip, err := wipCtl.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetRefID(ref.ID).SetCreatorID(operator.ID).SetRepositoryID(repository.ID)) if err != nil { w.Error(err) return } - if !bytes.Equal(commit.Hash, wip.BaseCommit) { + if !bytes.Equal(ref.CommitHash, wip.BaseCommit) { w.Error(fmt.Errorf("base commit not equal with branch, please update wip")) return } + + var commit *models.Commit + if !ref.CommitHash.IsEmpty() { + commit, err = wipCtl.Repo.CommitRepo().Commit(ctx, ref.CommitHash) + if err != nil { + w.Error(err) + return + } + } + var msg string if params.Msg != nil { msg = *params.Msg @@ -232,7 +236,8 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon } wip.BaseCommit = commit.Commit().Hash //set for response - err = repo.WipRepo().UpdateByID(ctx, models.NewUpdateWipParams(wip.ID).SetBaseCommit(wip.BaseCommit)) + wip.CurrentTree = commit.Commit().TreeHash + err = repo.WipRepo().UpdateByID(ctx, models.NewUpdateWipParams(wip.ID).SetBaseCommit(wip.BaseCommit).SetCurrentTree(wip.CurrentTree)) if err != nil { return err } @@ -244,7 +249,7 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon return } - w.JSON(wip) + w.JSON(wip, http.StatusCreated) } // DeleteWip delete a active working in process operator only can delete himself wip diff --git a/integrationtest/helper.go b/integrationtest/helper.go index e12adba9..8759b848 100644 --- a/integrationtest/helper.go +++ b/integrationtest/helper.go @@ -3,6 +3,7 @@ package integrationtest import ( "bytes" "context" + "crypto/rand" "errors" "fmt" "io" @@ -12,7 +13,9 @@ import ( "testing" "time" + "github.com/google/uuid" "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/utils" "github.com/smartystreets/goconvey/convey" "github.com/jiaozifs/jiaozifs/testhelper" @@ -141,3 +144,27 @@ func createRepo(ctx context.Context, c convey.C, client *api.Client, repoName st convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) }) } + +func uploadRandomObject(ctx context.Context, c convey.C, client *api.Client, user string, repoName string, refName string, path string) { //nolint + c.Convey("upload object "+uuid.New().String(), func(c convey.C) { + c.Convey("success upload object", func() { + resp, err := client.UploadObjectWithBody(ctx, user, repoName, &api.UploadObjectParams{ + Branch: refName, + Path: path, + }, "application/octet-stream", io.LimitReader(rand.Reader, 50)) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + }) + }) +} + +func commitWip(ctx context.Context, c convey.C, client *api.Client, user string, repoName string, refName string) { + c.Convey("commit wip "+uuid.New().String(), func() { + resp, err := client.CommitWip(ctx, user, repoName, &api.CommitWipParams{ + RefName: refName, + Msg: utils.String("test commit msg"), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + }) +} diff --git a/integrationtest/objects_test.go b/integrationtest/objects_test.go new file mode 100644 index 00000000..71d8ed13 --- /dev/null +++ b/integrationtest/objects_test.go @@ -0,0 +1,377 @@ +package integrationtest + +import ( + "bytes" + "context" + "encoding/hex" + "fmt" + "io" + "net/http" + + "github.com/jiaozifs/jiaozifs/utils/hash" + + "github.com/jiaozifs/jiaozifs/utils" + + "github.com/jiaozifs/jiaozifs/api" + apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" + "github.com/smartystreets/goconvey/convey" +) + +func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { + client, _ := api.NewClient(urlStr + apiimpl.APIV1Prefix) + return func(c convey.C) { + userName := "molly" + repoName := "dataspace" + refName := "feat/obj_test" + + createUser(ctx, c, client, userName) + loginAndSwitch(ctx, c, client, userName) + createRepo(ctx, c, client, repoName) + createBranch(ctx, c, client, userName, repoName, "main", refName) + createWip(ctx, c, client, userName, repoName, refName) + + c.Convey("upload object", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.UploadObjectWithBody(ctx, userName, repoName, &api.UploadObjectParams{ + Branch: refName, + Path: "a.bin", + }, "application/octet-stream", bytes.NewReader([]byte{1, 2, 3, 4, 5, 6, 7, 8})) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("fail to create branch in non exit user", func() { + resp, err := client.UploadObjectWithBody(ctx, "mockuser", "main", &api.UploadObjectParams{ + Branch: refName, + Path: "a.bin", + }, "application/octet-stream", bytes.NewReader([]byte{1, 2, 3, 4, 5, 6, 7, 8})) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to upload in non exit repo", func() { + resp, err := client.UploadObjectWithBody(ctx, userName, "fakerepo", &api.UploadObjectParams{ + Branch: refName, + Path: "a.bin", + }, "application/octet-stream", bytes.NewReader([]byte{1, 2, 3, 4, 5, 6, 7, 8})) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to upload object in non exit branch", func() { + resp, err := client.UploadObjectWithBody(ctx, userName, repoName, &api.UploadObjectParams{ + Branch: "mockref", + Path: "a.bin", + }, "application/octet-stream", bytes.NewReader([]byte{1, 2, 3, 4, 5, 6, 7, 8})) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to upload object in no wip ", func() { + resp, err := client.UploadObjectWithBody(ctx, userName, repoName, &api.UploadObjectParams{ + Branch: "main", + Path: "a.bin", + }, "application/octet-stream", bytes.NewReader([]byte{1, 2, 3, 4, 5, 6, 7, 8})) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("forbidden upload object in others", func() { + resp, err := client.UploadObjectWithBody(ctx, "jimmy", "happygo", &api.UploadObjectParams{ + Branch: "main", + Path: "a.bin", + }, "application/octet-stream", bytes.NewReader([]byte{1, 2, 3, 4, 5, 6, 7, 8})) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + + c.Convey("empty path", func() { + resp, err := client.UploadObjectWithBody(ctx, userName, repoName, &api.UploadObjectParams{ + Branch: refName, + }, "application/octet-stream", bytes.NewReader([]byte{1, 2, 3, 4, 5, 6, 7, 8})) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + + c.Convey("success upload object", func() { + resp, err := client.UploadObjectWithBody(ctx, userName, repoName, &api.UploadObjectParams{ + Branch: refName, + Path: "a.bin", + }, "application/octet-stream", bytes.NewReader([]byte{1, 2, 3, 4, 5, 6, 7, 8})) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + }) + + c.Convey("success upload object on subpath", func() { + resp, err := client.UploadObjectWithBody(ctx, userName, repoName, &api.UploadObjectParams{ + Branch: refName, + Path: "a/b.bin", + }, "application/octet-stream", bytes.NewReader([]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 1, 1, 1})) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + }) + }) + + //commit object to branch + c.Convey("commit wip", func() { + resp, err := client.CommitWip(ctx, userName, repoName, &api.CommitWipParams{ + RefName: refName, + Msg: utils.String("test commit msg"), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + }) + + c.Convey("head object", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ + Branch: refName, + Path: "a.bin", + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("fail to head object in non exit user", func() { + resp, err := client.HeadObject(ctx, "mock user", repoName, &api.HeadObjectParams{ + Branch: refName, + Path: "a.bin", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to head object in non exit repo", func() { + resp, err := client.HeadObject(ctx, userName, "fakerepo", &api.HeadObjectParams{ + Branch: refName, + Path: "a.bin", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to head object in non exit branch", func() { + resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ + Branch: "mockref", + Path: "a.bin", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("forbidden head object in others", func() { + resp, err := client.HeadObject(ctx, "jimmy", "happygo", &api.HeadObjectParams{ + Branch: refName, + Path: "a.bin", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + + c.Convey("empty path", func() { + resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ + Branch: refName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + + c.Convey("no path", func() { + resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ + Branch: refName, + Path: "c/d.txt", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + + c.Convey("success to head object", func() { + resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ + Branch: refName, + Path: "a.bin", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + etag := resp.Header.Get("ETag") + convey.So(etag, convey.ShouldEqual, `"0ee0646c1c77d8131cc8f4ee65c7673b"`) + }) + }) + + c.Convey("get object", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ + Branch: refName, + Path: "a.bin", + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("fail to get object in non exit user", func() { + resp, err := client.GetObject(ctx, "mock user", repoName, &api.GetObjectParams{ + Branch: refName, + Path: "a.bin", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to get object in non exit repo", func() { + resp, err := client.GetObject(ctx, userName, "fakerepo", &api.GetObjectParams{ + Branch: refName, + Path: "a.bin", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to get object in non exit branch", func() { + resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ + Branch: "mockref", + Path: "a.bin", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("forbidden get object in others", func() { + resp, err := client.GetObject(ctx, "jimmy", "happygo", &api.GetObjectParams{ + Branch: refName, + Path: "a.bin", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + + c.Convey("empty path", func() { + resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ + Branch: refName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + + c.Convey("no path", func() { + resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ + Branch: refName, + Path: "c/d.txt", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + + c.Convey("success to get object", func() { + resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ + Branch: refName, + Path: "a.bin", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + reader := hash.NewHashingReader(resp.Body, hash.Md5) + data, err := io.ReadAll(reader) + fmt.Println(data) + convey.So(err, convey.ShouldBeNil) + etag := resp.Header.Get("ETag") + + exectEtag := fmt.Sprintf(`"%s"`, hex.EncodeToString(reader.Md5.Sum(nil))) + convey.So(etag, convey.ShouldEqual, exectEtag) + }) + }) + + c.Convey("delete object", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.DeleteObject(ctx, userName, repoName, &api.DeleteObjectParams{ + Branch: refName, + Path: "a/b.bin", + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("fail to delete object in non exit user", func() { + resp, err := client.DeleteObject(ctx, "mockUser", repoName, &api.DeleteObjectParams{ + Branch: refName, + Path: "a/b.bin", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to delete object in non exit repo", func() { + resp, err := client.DeleteObject(ctx, userName, "fakerepo", &api.DeleteObjectParams{ + Branch: refName, + Path: "a/b.bin", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to delete object in non exit branch", func() { + resp, err := client.DeleteObject(ctx, userName, repoName, &api.DeleteObjectParams{ + Branch: "mockref", + Path: "a/b.bin", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("forbidden delete object in others", func() { + resp, err := client.DeleteObject(ctx, "jimmy", "happygo", &api.DeleteObjectParams{ + Branch: "main", + Path: "a/b.bin", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + + c.Convey("empty path", func() { + resp, err := client.DeleteObject(ctx, userName, repoName, &api.DeleteObjectParams{ + Branch: refName, + Path: "", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + + c.Convey("no path", func() { + resp, err := client.DeleteObject(ctx, userName, repoName, &api.DeleteObjectParams{ + Branch: refName, + Path: "e/m.txt", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + + c.Convey("success to delete object", func(c convey.C) { + resp, err := client.DeleteObject(ctx, userName, repoName, &api.DeleteObjectParams{ + Branch: refName, + Path: "a/b.bin", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + commitWip(ctx, c, client, userName, repoName, refName) + + resp, err = client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ + Branch: refName, + Path: "a/b.bin", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + }) + } +} diff --git a/integrationtest/root_test.go b/integrationtest/root_test.go index 129417e8..2511f2a6 100644 --- a/integrationtest/root_test.go +++ b/integrationtest/root_test.go @@ -16,4 +16,5 @@ func TestSpec(t *testing.T) { convey.Convey("repo test", t, RepoSpec(ctx, urlStr)) convey.Convey("branch test", t, BranchSpec(ctx, urlStr)) convey.Convey("branch test", t, WipSpec(ctx, urlStr)) + convey.Convey("branch test", t, ObjectSpec(ctx, urlStr)) } diff --git a/integrationtest/wip_test.go b/integrationtest/wip_test.go index 2c5ea665..e9e98014 100644 --- a/integrationtest/wip_test.go +++ b/integrationtest/wip_test.go @@ -241,7 +241,7 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { } func createWip(ctx context.Context, c convey.C, client *api.Client, user string, repoName string, refName string) { - c.Convey("create branch "+refName, func() { + c.Convey("create wip "+refName, func() { resp, err := client.CreateWip(ctx, user, repoName, &api.CreateWipParams{ RefName: refName, }) diff --git a/models/tree.go b/models/tree.go index 32f83e3a..921b4216 100644 --- a/models/tree.go +++ b/models/tree.go @@ -130,6 +130,7 @@ func (blob *Blob) FileTree() *FileTree { Hash: blob.Hash, Type: blob.Type, Size: blob.Size, + CheckSum: blob.CheckSum, Properties: blob.Properties, CreatedAt: blob.CreatedAt, UpdatedAt: blob.UpdatedAt, diff --git a/utils/hash/hashing_reader.go b/utils/hash/hashing_reader.go index 5eecee7b..85006ec8 100644 --- a/utils/hash/hashing_reader.go +++ b/utils/hash/hashing_reader.go @@ -105,11 +105,17 @@ type HashingReader struct { func (s *HashingReader) Read(p []byte) (int, error) { nb, err := s.originalReader.Read(p) - if err != nil { - return nb, err - } s.CopiedSize += int64(nb) - _, err = s.Hasher.Write(p[0:nb]) + if s.Md5 != nil { + if _, err2 := s.Md5.Write(p[0:nb]); err2 != nil { + return nb, err2 + } + } + if s.Sha256 != nil { + if _, err2 := s.Sha256.Write(p[0:nb]); err2 != nil { + return nb, err2 + } + } return nb, err } From 7bb89e6a2a6d47e859360efac343bec8f1972194 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Wed, 20 Dec 2023 10:52:15 +0800 Subject: [PATCH 094/210] feat: add reposotory id in commit/tag/tree table --- api/jiaozifs.gen.go | 180 ++++++++++++++++++++++------------ api/swagger.yml | 10 ++ controller/commit_ctl.go | 24 +++-- controller/object_ctl.go | 51 +++++++--- controller/repository_ctl.go | 11 ++- controller/wip_ctl.go | 12 +-- models/commit.go | 24 ++++- models/commit_test.go | 14 ++- models/repo.go | 20 ++-- models/repo_test.go | 8 +- models/tag.go | 19 +++- models/tag_test.go | 16 ++- models/tree.go | 141 ++++++++++++++++---------- models/tree_test.go | 14 ++- versionmgr/commit.go | 78 +++++++-------- versionmgr/commit_node.go | 5 + versionmgr/commit_test.go | 57 +++++------ versionmgr/merge_base_test.go | 14 ++- versionmgr/worktree.go | 15 ++- versionmgr/worktree_test.go | 11 ++- 20 files changed, 468 insertions(+), 256 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 00be6513..aeb132d0 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -264,6 +264,9 @@ type DeleteObjectParams struct { // GetObjectParams defines parameters for GetObject. type GetObjectParams struct { + // IsWip isWip indicate to retrieve from working in progress, default false + IsWip *bool `form:"isWip,omitempty" json:"isWip,omitempty"` + // Branch branch to the ref Branch string `form:"branch" json:"branch"` @@ -276,6 +279,9 @@ type GetObjectParams struct { // HeadObjectParams defines parameters for HeadObject. type HeadObjectParams struct { + // IsWip isWip indicate to retrieve from working in progress, default false + IsWip *bool `form:"isWip,omitempty" json:"isWip,omitempty"` + // Branch branch to the ref Branch string `form:"branch" json:"branch"` @@ -1144,6 +1150,22 @@ func NewGetObjectRequest(server string, owner string, repository string, params if params != nil { queryValues := queryURL.Query() + if params.IsWip != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "isWip", runtime.ParamLocationQuery, *params.IsWip); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "branch", runtime.ParamLocationQuery, params.Branch); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { @@ -1230,6 +1252,22 @@ func NewHeadObjectRequest(server string, owner string, repository string, params if params != nil { queryValues := queryURL.Query() + if params.IsWip != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "isWip", runtime.ParamLocationQuery, *params.IsWip); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "branch", runtime.ParamLocationQuery, params.Branch); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { @@ -4691,6 +4729,14 @@ func (siw *ServerInterfaceWrapper) GetObject(w http.ResponseWriter, r *http.Requ // Parameter object where we will unmarshal all parameters from the context var params GetObjectParams + // ------------- Optional query parameter "isWip" ------------- + + err = runtime.BindQueryParameter("form", true, false, "isWip", r.URL.Query(), ¶ms.IsWip) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "isWip", Err: err}) + return + } + // ------------- Required query parameter "branch" ------------- if paramValue := r.URL.Query().Get("branch"); paramValue != "" { @@ -4786,6 +4832,14 @@ func (siw *ServerInterfaceWrapper) HeadObject(w http.ResponseWriter, r *http.Req // Parameter object where we will unmarshal all parameters from the context var params HeadObjectParams + // ------------- Optional query parameter "isWip" ------------- + + err = runtime.BindQueryParameter("form", true, false, "isWip", r.URL.Query(), ¶ms.IsWip) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "isWip", Err: err}) + return + } + // ------------- Required query parameter "branch" ------------- if paramValue := r.URL.Query().Get("branch"); paramValue != "" { @@ -6173,69 +6227,69 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+RcW3PbNvb/Khj++9D+Vzdfmtmq0+nEjtN410k9tlM/xF4NRB5KSEiABUDLqsfffQcA", - "7wQpSpZv3ZeMQwIH5/rDOQeg7hyXhRGjQKVwxneOcOcQYv3n21jOgUriYkkYvWDfgKrHEWcRcElAD5Lp", - "Yw+Ey0mkhjpjB6N/XV4g/RLJOZbIZXHgoSmgWICHJEM4pw6Iw58xCCmcniOXEThjR0hO6My575kVJnAb", - "EY4N9epinym5RUcRc+eIUCTAZdRTpHzGQyydsUOofLOf0yZUwgy4c3/fc9TKhIPnjL8kslxn49j0K7hS", - "8XDAMXXnhxwyDspaoDgErY0q84LF3LW9qiytCWTDbSwczjGdQX3pt27KUlFci7A95wAL+IDF3MrpKZb2", - "FxesYU5FBE2gl/JjFYGFIZEWEWI5Z1z99R0H3xk7/zfMnXKYeOTwnMwoljGHnJSENWcpA4L3VpbU5WEJ", - "fUm0AWrSN+rrI/AZXOBZw0sh8AwaFM2BSkXXSE8khMI6MnmAOcdLbQkOzfb7HHnryVYxnybccy7UoF5q", - "kqKiCyLnAhaYqkhW1HaRO6tj6JFnEDFBJOPLuou8Kwa8RfpP9gCsyKhH2Rg4YTNCDxn1yay+9tnB28M6", - "6KinaEGCAHEIMaEIKJ4G4CFG0W+fjxHx0ZUDtxI4xcGVM0DoQuEgo8ESLRj/Jq7ogsg5whSlozQmIgH8", - "hrgwuKJOzwEah4pzQcIoID4BTz1MxhdEyTXh4yCYYvfbJFAyTQI8haDOvX6sYDgKsAuK58q8mAcDZzX5", - "mFuIGwTGfIk+n52oRZjvA1fIz4X6bywA+YwjTcK6iiHuMvaNwERho6ivYt4i/TbbVYRkHNTe4/TWCCyz", - "nI9JAN4kzGO3vGDyQi3jEREFeJkIwwVazBlS89UTTe1nhJEfBwESQCVQF8w2SATiQD3g4F1RQtGHi48n", - "CFMPhXiJXEal8iSMAkK/6U0S5brUZFEIcs487RsNWrOaJOIkLBikkwVYLO3E6kRmhM4Qi+VgJczkPFqt", - "XFrYFqm/67/OJTbpSjlS3Tm434SKGIvRlXaByol5UZXJ0EUheAQjaUCwRiIEiT0s8apNxxD7LIB/TGeo", - "2RqHt5e99Jyoac9WLyYh86C0GcSEyr1dKyVB/oLJdCmNHtdNnDK9JywlDCRqNHI3G7Okp/Gdgz2PKN3g", - "4LScajaEcU7vFM8IbUjR5lhMQsYtBvgEtxJFKrKJQPgGk0ABeS71lLEAMNUmxLeTCPgksgLER3xLQhwg", - "GodT4Ij5CKjkBASKgOsVlDYIJaFy0ZHNDhRu5YT5vgBZp69T8AzqOCjaNwpYANFUBpvbchBxIC0Q+ilj", - "9AYHMQjks5h6yg0VzXRaO88VV8jUXFFWzkVZSJtbnKnIqtrPJCKN6c8GqZ2ewvjxu3KQxMSzjV6VgXQk", - "86mpUsizn46UHprwHb9zKqv2ikpOWC2qaZ2U7gz8EyIsyX5UitE2FC1Ec9mJs429bbZyotpWX1FBgZd8", - "Abs0zanps3veB8Deo7jkVjws8SLN5KbOdA4yjtSeb6l/XRaGk4iDLyYhEULxUcM5yWNQCblCNTUe6fEI", - "c0DJnIEV7tMEJa0L2vytWEKoDTXlNs3gCSWS4ID8pVN4yuSk+OTapsu6HrJitqaGoxCToGQn0E/Wsffl", - "HOiGpk6sfJSsqSnZLKmqxSMqbXHUCO3H4h3hhTcFAzWXfbWVjYdtXmNaaQrgx9RnFq9cHxTcmKvyWdn4", - "mG488fi0nMBFN/u2OdDdXQIsNmAqn9WRo1jbZ50lVOVFO9X92chU8OsGY57BjAjZZNQ1lBZhIRaMa1wO", - "CT0BOlOp+j+3K0ZhHZtEfwAXhNEzvbHVxcERmdyYIXXI5DFVekfpAKuJJQhZJFEb0kg+4mzGcdhMviJ6", - "Pq7ItU3oSxLVRT3AAvLu49Mnj4cmRBX6vYzkMdtMVzaNN0kCKkZR2yG4MSdyea52S2OTKRbEneDYlLB6", - "G9Xorh7nVOdSRqZ6112CdDjJO0BqN9V60VxzigM9aiJAlF0LR+TfoPs9Xxdykh1cTAFz4O9TyUzvKGdH", - "v63yo0QiCUaUHfsrwewv4gv04eLiFL09PXZ6TkBcoALygwLnbYTdOaDdwcjpObrHogmL8XC4WCwGWL8e", - "MD4bJnPF8OT48OjT+VF/dzAazGUY6OSWyACKi5r1sqhzdgajwUiNZBFQHBFn7OzpR6ZC13YYKm0Ndaqj", - "A4eZrF2Fj06Njz1nbBqkjolJEPKAeUuTfOmeikGTKEiOioZfhYl5kxvZaoAcHbeDhy04eG+miYgpPSqq", - "u6PRWsy3pX22QzK9YqUlGrsuCOHHgem5OT1nDtgDrhk6B9k/NM5cWhhucRgFja79C566Huzs7v345md0", - "iuX8l+HP6IOU0e80WFoCU7G1P9qxtaCw7verVBT9gQPiaWmOOGcaA/Z3R5akmjEUYrrMD++01D5ONpvy", - "6ONEAHQO/AY4SmgXoMEZf7nuOSIOQ6yyMycCruAG4UxjEs+EsrsGgWs1N/NdFstW51Xv7V7QZic162Xq", - "zK4lI6VFTSYWhndsQYHfD+94tl/cm2UDMNtBWXHv9HPTpaurb7/OsVkHGXoeyrUZLDsp0gzaqw96z/iU", - "eB5QM8Ky9Ccm37OYeuvovqRJwzQyIgzQR1MYJv8X5qiHMok4yJhThFG6IgJll0FB88kc5/q+58zA4pG/", - "gcy0GmGOQ5AaCr5UmT5YSkAcU3PskLb89BlQCg26a/vLqL8z2t1zemZnNNiS74xn+uy4VwRkLJVzOWPn", - "P4bA999fXXn/31f/9H5Fv/7wjx++s0DI9VpQylwJsi8kBxyWkS1LJKaEKvVbwKpn9610qRKAHpqH/TTP", - "ti7V0sw+Sg5y81n1necEC9n/yDxzCtc6WA3fHb15Ks1EmEuCA/SYGkrnn6W3EB7sSo+i9b3RruWoFjzC", - "lWb0iVrEoS/IjIKnT8N8xnVfiKXxWFDaCXOzTmX7uh2RrRkyFbL4GX7tjBoH6oswCb2dNzZhNbqBh7Sp", - "FEqhcyyJ8Ik+1tgUHmcg6w5mA7x50o4sI94HwN7fCvIajEPMLaZXgU1dUATpMud/EUr+liHdkm2mJYa+", - "gALcZDUVENAHvYj4qOrvNiCoRDkxTqaPh5MY1emoUyzqJI+h12ZFK508nV2XWFkFU33NT8GOOf/0U3D5", - "MwZNO1nPjHvYWhwCLMkNrF4tkbX7Wte9hkLocxSwAgx3K+Yfkqr0nDAOJFHQMlSj++kZf1NnoMBD5X4G", - "DZYII5WSB4B8EoA+VI+1RGgxJ+4chbGQaGquBHnoKiV25QyK1ylamO3QOdjZWuegeJOlOd0NCxdI9m07", - "j6303Khe3azsinlQBTpLBnbK9bUWfa3jvb5mtWYeUsOXnnPbv8mk6MOtG8Qe9Kfal1WA6LpXA8OGZe9Z", - "GVQ6dg707TBTSRZQaZvG62IsW2FbAslUn+pha5m6UgtbiYXCKpZQUKnnS1FmhReLJl/8tteyPVSOSDfv", - "97YZu7bMfbmxq6N3zZAzp4cvxkvq7NQcpR2ehkmKsRKlDtJUxOZ2lUSCg5+c06/lLKvbfknelADNhl2/", - "fVviaz6I0Blve3fvYuud1USaLNdL7WceQHt37+nNsk0w9m0onCji9RpUQXe7NV8vdJsT7INiXbJt2K58", - "A9UJtHcedfXK9wBaBYmFUxBabxvo7rGjn1qGHjLqB8SVAl0SOUcXmCuYeDpHL2nC7uuddh9jRSvEnRCR", - "YJy+tP+YWKSvkTbiEQr061cLSop9NM01+UpxaYU/ufruTbM7/QbSXM8Rx7SUf7Z2ijn43xs9DSWe/YCS", - "uwDte+zj7amdriUnt5DqN5OtVU+qt/pGlrxBhL76cmS170SYw/BuigXMAXv3q93oHfH9Vd4jInCJT1yk", - "pOgh4us+RvY0OeZNv+BQemZMtrfontu3zCfCHXzLeA/ylJq2XSj9aCuUkp5y1mOGhvyswBhwoG4JE83L", - "V9NbthBLXXjLAaKdSAzb4uLI+LGC15cVGb3G1Tn4KNlbVYmvP7LNcpoGkH/+IMxvuHeOwyfvVXTr5dbz", - "lKLJta5fY2TqcBIg46gtXgqfnDxieltYxeId2bVOzS0yn5SsddjXiMXEBRTT/GPHtot42aFfmZ+K+RlN", - "ygr9QfSQJzfpm2/lpXftH6vLWL3O33ycU80q1STD6Moy8gB7KDmeRf3CsQp6GXcnlS1QUSD79cDUZBFr", - "r/jy1Px3v3DxFTylbOcp0PWs1OBdBa8atF5KT7jKjC1Xb+ntPHpbvrbMI3R4HsHGFBYvxsRJ42VV29+E", - "m/q3bQfKPi57xP0nW8Oi2PP8KrS+++UbNDHDH649k4c8uKFLqDnYV5jLki88zcc2gf7JjBl4faI/mudt", - "2Jcm1utgYNfufsROOfjk9vlz0/UiK3fjQpPthaCn/qmMtFBIE8on6X3o9LHwXVtT+P6RfbH2aNFb/r7P", - "djG48pVdW8qQVHXpFEw9ZPkG0JbwLUi04RWLSxJteLdiQaLn9UcOIbsB/UtOhM6UO0ac6VQx15Jisu2Q", - "sFn8rbiHIm9xCgvLz36jopMaV0fzVns1G5WptQZ1t6b01k4ArS618/QuhZIP4J+htfGj7ROATpdbTfbW", - "wRfbUG/o6i5s61nHJYkOk1Grjzi27UG9+sVvOX+uvnaXdnY3f0v0+QKhLONtE0h7Cc2yZlfPf9Dz1d31", - "flJs1noy2Nwa7snxR5j9uqaNtVDMni0mGzaAhO80T9PJY8IC0r9yqar058jZHrIdGJks8SxZ/W5Fh40h", - "SH4Pq7G03EI+2KkMvDSGWFX/vbREUZeACxKVar+IM31PXrlcpch/RSBbrsvuij9Q8eVaLVb8sQzzpPSD", - "GF+uVdQbZ7bhSqFzb/ydehEzv/iR//rEeDgMmIuDORNyvLf/087eEEdkeLPj1NFzJcFs6vX9fwMAAP//", - "XWhxo2NcAAA=", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 96f969e9..91ffdea6 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -595,6 +595,11 @@ paths: operationId: getObject summary: get object content parameters: + - in: query + name: isWip + description: isWip indicate to retrieve from working in progress, default false + schema: + type: boolean - in: header name: Range description: Byte range to retrieve @@ -666,6 +671,11 @@ paths: operationId: headObject summary: check if object exists parameters: + - in: query + name: isWip + description: isWip indicate to retrieve from working in progress, default false + schema: + type: boolean - in: header name: Range description: Byte range to retrieve diff --git a/controller/commit_ctl.go b/controller/commit_ctl.go index f9c6ef2b..4a363ad3 100644 --- a/controller/commit_ctl.go +++ b/controller/commit_ctl.go @@ -58,13 +58,13 @@ func (commitCtl CommitController) GetEntriesInRef(ctx context.Context, w *api.Ji return } - commit, err := commitCtl.Repo.CommitRepo().Commit(ctx, ref.CommitHash) + commit, err := commitCtl.Repo.CommitRepo(repository.ID).Commit(ctx, ref.CommitHash) if err != nil { w.Error(err) return } - workTree, err := versionmgr.NewWorkTree(ctx, commitCtl.Repo.FileTreeRepo(), models.NewRootTreeEntry(commit.TreeHash)) + workTree, err := versionmgr.NewWorkTree(ctx, commitCtl.Repo.FileTreeRepo(repository.ID), models.NewRootTreeEntry(commit.TreeHash)) if err != nil { w.Error(err) return @@ -82,14 +82,26 @@ func (commitCtl CommitController) GetEntriesInRef(ctx context.Context, w *api.Ji w.JSON(treeEntry) } -func (commitCtl CommitController) GetCommitDiff(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, _ string, basehead string, params api.GetCommitDiffParams) { +func (commitCtl CommitController) GetCommitDiff(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, basehead string, params api.GetCommitDiffParams) { operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return } - if operator.Name == ownerName { //todo check permission + owner, err := commitCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + repository, err := commitCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) + if err != nil { + w.Error(err) + return + } + + if operator.ID == owner.ID { //todo check permission w.Forbidden() return } @@ -111,7 +123,7 @@ func (commitCtl CommitController) GetCommitDiff(ctx context.Context, w *api.Jiao return } - bashCommit, err := commitCtl.Repo.CommitRepo().Commit(ctx, bashCommitHash) + bashCommit, err := commitCtl.Repo.CommitRepo(repository.ID).Commit(ctx, bashCommitHash) if err != nil { w.Error(err) return @@ -122,7 +134,7 @@ func (commitCtl CommitController) GetCommitDiff(ctx context.Context, w *api.Jiao path = *params.Path } - commitOp := versionmgr.NewCommitOp(commitCtl.Repo, bashCommit) + commitOp := versionmgr.NewCommitOp(commitCtl.Repo, repository.ID, bashCommit) changes, err := commitOp.DiffCommit(ctx, toCommitHash) if err != nil { w.Error(err) diff --git a/controller/object_ctl.go b/controller/object_ctl.go index 793fccaf..ece8b75c 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -69,7 +69,7 @@ func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsRes w.Error(err) return } - commit, err := oct.Repo.CommitRepo().Commit(ctx, ref.CommitHash) + commit, err := oct.Repo.CommitRepo(repository.ID).Commit(ctx, ref.CommitHash) if err != nil { w.Error(err) return @@ -81,7 +81,7 @@ func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsRes return } - workTree, err := versionmgr.NewWorkTree(ctx, oct.Repo.FileTreeRepo(), models.NewRootTreeEntry(commit.TreeHash)) + workTree, err := versionmgr.NewWorkTree(ctx, oct.Repo.FileTreeRepo(repository.ID), models.NewRootTreeEntry(commit.TreeHash)) if err != nil { w.Error(err) return @@ -102,7 +102,7 @@ func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsRes } func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, ownerName string, repositoryName string, params api.GetObjectParams) { //nolint - user, err := auth.GetOperator(ctx) + operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return @@ -114,7 +114,7 @@ func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsRespon return } - if user.Name != ownerName { //todo check permission + if operator.Name != ownerName { //todo check permission w.Forbidden() return } @@ -132,16 +132,26 @@ func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsRespon } treeHash := hash.EmptyHash - if !ref.CommitHash.IsEmpty() { - commit, err := oct.Repo.CommitRepo().Commit(ctx, ref.CommitHash) + if utils.BoolValue(params.IsWip) { + wip, err := oct.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetCreatorID(operator.ID).SetRepositoryID(repository.ID).SetRefID(ref.ID)) if err != nil { w.Error(err) return } - treeHash = commit.TreeHash + treeHash = wip.CurrentTree + } else { + + if !ref.CommitHash.IsEmpty() { + commit, err := oct.Repo.CommitRepo(repository.ID).Commit(ctx, ref.CommitHash) + if err != nil { + w.Error(err) + return + } + treeHash = commit.TreeHash + } } - workTree, err := versionmgr.NewWorkTree(ctx, oct.Repo.FileTreeRepo(), models.NewRootTreeEntry(treeHash)) + workTree, err := versionmgr.NewWorkTree(ctx, oct.Repo.FileTreeRepo(repository.ID), models.NewRootTreeEntry(treeHash)) if err != nil { w.Error(err) return @@ -199,7 +209,7 @@ func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsRespon } func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, ownerName string, repositoryName string, params api.HeadObjectParams) { //nolint - user, err := auth.GetOperator(ctx) + operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return @@ -211,7 +221,7 @@ func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsRespo return } - if user.Name != ownerName { //todo check permission + if operator.Name != ownerName { //todo check permission w.Forbidden() return } @@ -221,7 +231,6 @@ func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsRespo w.Error(err) return } - ref, err := oct.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetRepositoryID(repository.ID).SetName(params.Branch)) if err != nil { w.Error(err) @@ -229,16 +238,26 @@ func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsRespo } treeHash := hash.EmptyHash - if !ref.CommitHash.IsEmpty() { - commit, err := oct.Repo.CommitRepo().Commit(ctx, ref.CommitHash) + if utils.BoolValue(params.IsWip) { + wip, err := oct.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetCreatorID(operator.ID).SetRepositoryID(repository.ID).SetRefID(ref.ID)) if err != nil { w.Error(err) return } - treeHash = commit.TreeHash + treeHash = wip.CurrentTree + } else { + + if !ref.CommitHash.IsEmpty() { + commit, err := oct.Repo.CommitRepo(repository.ID).Commit(ctx, ref.CommitHash) + if err != nil { + w.Error(err) + return + } + treeHash = commit.TreeHash + } } - fileRepo := oct.Repo.FileTreeRepo() + fileRepo := oct.Repo.FileTreeRepo(repository.ID) workTree, err := versionmgr.NewWorkTree(ctx, fileRepo, models.NewRootTreeEntry(treeHash)) if err != nil { w.Error(err) @@ -370,7 +389,7 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes var response api.ObjectStats err = oct.Repo.Transaction(ctx, func(dRepo models.IRepo) error { - workingTree, err := versionmgr.NewWorkTree(ctx, dRepo.FileTreeRepo(), models.NewRootTreeEntry(stash.CurrentTree)) + workingTree, err := versionmgr.NewWorkTree(ctx, dRepo.FileTreeRepo(repository.ID), models.NewRootTreeEntry(stash.CurrentTree)) if err != nil { return err } diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index 7a597c21..0a59ee64 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -269,17 +269,17 @@ func (repositoryCtl RepositoryController) GetCommitsInRepository(ctx context.Con return } - repo, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) + repository, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) if err != nil { w.Error(err) return } - refName := repo.HEAD + refName := repository.HEAD if params.RefName != nil { refName = *params.RefName } - ref, err := repositoryCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetRepositoryID(repo.ID).SetName(refName)) + ref, err := repositoryCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetRepositoryID(repository.ID).SetName(refName)) if err != nil { w.Error(err) return @@ -290,14 +290,15 @@ func (repositoryCtl RepositoryController) GetCommitsInRepository(ctx context.Con return } - commit, err := repositoryCtl.Repo.CommitRepo().Commit(ctx, ref.CommitHash) + commit, err := repositoryCtl.Repo.CommitRepo(repository.ID).Commit(ctx, ref.CommitHash) if err != nil { w.Error(err) return } var commits []api.Commit - iter := versionmgr.NewCommitPreorderIter(versionmgr.NewCommitNode(ctx, commit, repositoryCtl.Repo.CommitRepo()), nil, nil) + commitNode := versionmgr.NewCommitNode(ctx, commit, repositoryCtl.Repo.CommitRepo(repository.ID)) + iter := versionmgr.NewCommitPreorderIter(commitNode, nil, nil) for { commit, err := iter.Next() if err == nil { diff --git a/controller/wip_ctl.go b/controller/wip_ctl.go index cb2b5d46..dc134f7b 100644 --- a/controller/wip_ctl.go +++ b/controller/wip_ctl.go @@ -71,7 +71,7 @@ func (wipCtl WipController) CreateWip(ctx context.Context, w *api.JiaozifsRespon currentTreeHash := hash.EmptyHash if !ref.CommitHash.IsEmpty() { - baseCommit, err := wipCtl.Repo.CommitRepo().Commit(ctx, ref.CommitHash) + baseCommit, err := wipCtl.Repo.CommitRepo(repository.ID).Commit(ctx, ref.CommitHash) if err != nil { w.Error(err) return @@ -215,7 +215,7 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon var commit *models.Commit if !ref.CommitHash.IsEmpty() { - commit, err = wipCtl.Repo.CommitRepo().Commit(ctx, ref.CommitHash) + commit, err = wipCtl.Repo.CommitRepo(repository.ID).Commit(ctx, ref.CommitHash) if err != nil { w.Error(err) return @@ -229,7 +229,7 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon //add commit err = wipCtl.Repo.Transaction(ctx, func(repo models.IRepo) error { - commitOp := versionmgr.NewCommitOp(repo, commit) + commitOp := versionmgr.NewCommitOp(repo, repository.ID, commit) commit, err := commitOp.AddCommit(ctx, operator, wip.ID, msg) if err != nil { return err @@ -252,7 +252,7 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon w.JSON(wip, http.StatusCreated) } -// DeleteWip delete a active working in process operator only can delete himself wip +// DeleteWip delete active working in process operator only can delete himself wip func (wipCtl WipController) DeleteWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.DeleteWipParams) { operator, err := auth.GetOperator(ctx) if err != nil { @@ -339,13 +339,13 @@ func (wipCtl WipController) GetWipChanges(ctx context.Context, w *api.JiaozifsRe return } - commit, err := wipCtl.Repo.CommitRepo().Commit(ctx, wip.BaseCommit) + commit, err := wipCtl.Repo.CommitRepo(repository.ID).Commit(ctx, wip.BaseCommit) if err != nil { w.Error(err) return } - workTree, err := versionmgr.NewWorkTree(ctx, wipCtl.Repo.FileTreeRepo(), models.NewRootTreeEntry(commit.TreeHash)) + workTree, err := versionmgr.NewWorkTree(ctx, wipCtl.Repo.FileTreeRepo(repository.ID), models.NewRootTreeEntry(commit.TreeHash)) if err != nil { w.Error(err) return diff --git a/models/commit.go b/models/commit.go index 7ccc0fe0..a6bd4f88 100644 --- a/models/commit.go +++ b/models/commit.go @@ -4,6 +4,7 @@ import ( "context" "time" + "github.com/google/uuid" "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/uptrace/bun" ) @@ -21,6 +22,7 @@ type Signature struct { type Commit struct { bun.BaseModel `bun:"table:commits"` Hash hash.Hash `bun:"hash,pk,type:bytea"` + RepositoryID uuid.UUID `bun:"repository_id,pk,type:uuid,notnull"` //////********commit********//////// // Author is the original author of the commit. Author Signature `bun:"author,type:jsonb"` @@ -107,20 +109,31 @@ func (commit *Commit) NumParents() int { } type ICommitRepo interface { + RepositoryID() uuid.UUID Commit(ctx context.Context, hash hash.Hash) (*Commit, error) Insert(ctx context.Context, commit *Commit) (*Commit, error) } type CommitRepo struct { - db bun.IDB + db bun.IDB + repositoryID uuid.UUID } -func NewCommitRepo(db bun.IDB) ICommitRepo { - return &CommitRepo{db} +func (cr CommitRepo) RepositoryID() uuid.UUID { + return cr.repositoryID +} + +func NewCommitRepo(db bun.IDB, repoID uuid.UUID) ICommitRepo { + return &CommitRepo{ + db: db, + repositoryID: repoID, + } } func (cr CommitRepo) Commit(ctx context.Context, hash hash.Hash) (*Commit, error) { commit := &Commit{} - err := cr.db.NewSelect().Model(commit).Where("hash = ?", hash).Scan(ctx) + err := cr.db.NewSelect().Model(commit). + Where("repository_id = ?", cr.repositoryID). + Where("hash = ?", hash).Scan(ctx) if err != nil { return nil, err } @@ -128,6 +141,9 @@ func (cr CommitRepo) Commit(ctx context.Context, hash hash.Hash) (*Commit, error } func (cr CommitRepo) Insert(ctx context.Context, commit *Commit) (*Commit, error) { + if commit.RepositoryID != cr.repositoryID { + return nil, ErrRepoIDMisMatch + } _, err := cr.db.NewInsert().Model(commit).Exec(ctx) if err != nil { return nil, err diff --git a/models/commit_test.go b/models/commit_test.go index 994564d9..ede2f717 100644 --- a/models/commit_test.go +++ b/models/commit_test.go @@ -4,6 +4,8 @@ import ( "context" "testing" + "github.com/google/uuid" + "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" "github.com/jiaozifs/jiaozifs/models" @@ -16,14 +18,24 @@ func TestCommitRepo(t *testing.T) { postgres, _, db := testhelper.SetupDatabase(ctx, t) defer postgres.Stop() //nolint - commitRepo := models.NewCommitRepo(db) + repoID := uuid.New() + commitRepo := models.NewCommitRepo(db, repoID) + require.Equal(t, commitRepo.RepositoryID(), repoID) commitModel := &models.Commit{} require.NoError(t, gofakeit.Struct(commitModel)) + commitModel.RepositoryID = repoID newCommitModel, err := commitRepo.Insert(ctx, commitModel) require.NoError(t, err) commitModel, err = commitRepo.Commit(ctx, commitModel.Hash) require.NoError(t, err) require.True(t, cmp.Equal(commitModel, newCommitModel, dbTimeCmpOpt)) + + t.Run("mis match repo id", func(t *testing.T) { + mistMatchModel := &models.Commit{} + require.NoError(t, gofakeit.Struct(mistMatchModel)) + _, err := commitRepo.Insert(ctx, mistMatchModel) + require.ErrorIs(t, err, models.ErrRepoIDMisMatch) + }) } diff --git a/models/repo.go b/models/repo.go index f3d69780..1d7cdaef 100644 --- a/models/repo.go +++ b/models/repo.go @@ -4,6 +4,8 @@ import ( "context" "database/sql" + "github.com/google/uuid" + "github.com/uptrace/bun" ) @@ -27,9 +29,9 @@ func IsolationLevelOption(level sql.IsolationLevel) TxOption { type IRepo interface { Transaction(ctx context.Context, fn func(repo IRepo) error, opts ...TxOption) error UserRepo() IUserRepo - FileTreeRepo() IFileTreeRepo - CommitRepo() ICommitRepo - TagRepo() ITagRepo + FileTreeRepo(repoID uuid.UUID) IFileTreeRepo + CommitRepo(repoID uuid.UUID) ICommitRepo + TagRepo(repoID uuid.UUID) ITagRepo RefRepo() IRefRepo RepositoryRepo() IRepositoryRepo WipRepo() IWipRepo @@ -59,16 +61,16 @@ func (repo *PgRepo) UserRepo() IUserRepo { return NewUserRepo(repo.db) } -func (repo *PgRepo) FileTreeRepo() IFileTreeRepo { - return NewFileTree(repo.db) +func (repo *PgRepo) FileTreeRepo(repoID uuid.UUID) IFileTreeRepo { + return NewFileTree(repo.db, repoID) } -func (repo *PgRepo) CommitRepo() ICommitRepo { - return NewCommitRepo(repo.db) +func (repo *PgRepo) CommitRepo(repoID uuid.UUID) ICommitRepo { + return NewCommitRepo(repo.db, repoID) } -func (repo *PgRepo) TagRepo() ITagRepo { - return NewTagRepo(repo.db) +func (repo *PgRepo) TagRepo(repoID uuid.UUID) ITagRepo { + return NewTagRepo(repo.db, repoID) } func (repo *PgRepo) RefRepo() IRefRepo { diff --git a/models/repo_test.go b/models/repo_test.go index bf05c729..d2970328 100644 --- a/models/repo_test.go +++ b/models/repo_test.go @@ -34,10 +34,12 @@ func TestRepoTransaction(t *testing.T) { t.Run("transaction", func(t *testing.T) { pgRepo := models.NewRepo(db) + repoID := uuid.New() err := pgRepo.Transaction(ctx, func(repo models.IRepo) error { - object := &models.FileTree{} - require.NoError(t, gofakeit.Struct(object)) - _, err := repo.FileTreeRepo().Insert(ctx, object) + treeNode := &models.FileTree{} + require.NoError(t, gofakeit.Struct(treeNode)) + treeNode.RepositoryID = repoID + _, err := repo.FileTreeRepo(repoID).Insert(ctx, treeNode) require.NoError(t, err) return err }) diff --git a/models/tag.go b/models/tag.go index 38f8fe21..e219431f 100644 --- a/models/tag.go +++ b/models/tag.go @@ -4,6 +4,8 @@ import ( "context" "time" + "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/uptrace/bun" ) @@ -11,6 +13,7 @@ import ( type Tag struct { bun.BaseModel `bun:"table:tags"` Hash hash.Hash `bun:"hash,pk,type:bytea"` + RepositoryID uuid.UUID `bun:"repository_id,pk,type:uuid,notnull"` Type ObjectType `bun:"type"` //////********commit********//////// // Name of the tag. @@ -29,19 +32,28 @@ type Tag struct { } type ITagRepo interface { + RepositoryID() uuid.UUID Insert(ctx context.Context, tag *Tag) (*Tag, error) Tag(ctx context.Context, hash hash.Hash) (*Tag, error) } type TagRepo struct { - db bun.IDB + db bun.IDB + repositoryID uuid.UUID +} + +func NewTagRepo(db bun.IDB, repID uuid.UUID) ITagRepo { + return &TagRepo{db: db, repositoryID: repID} } -func NewTagRepo(db bun.IDB) ITagRepo { - return &TagRepo{db: db} +func (t *TagRepo) RepositoryID() uuid.UUID { + return t.repositoryID } func (t *TagRepo) Insert(ctx context.Context, tag *Tag) (*Tag, error) { + if tag.RepositoryID != t.repositoryID { + return nil, ErrRepoIDMisMatch + } _, err := t.db.NewInsert(). Model(tag). Exec(ctx) @@ -55,6 +67,7 @@ func (t *TagRepo) Tag(ctx context.Context, hash hash.Hash) (*Tag, error) { tag := &Tag{} err := t.db.NewSelect(). Model(tag). + Where("repository_id = ?", t.repositoryID). Where("hash = ?", hash). Scan(ctx) if err != nil { diff --git a/models/tag_test.go b/models/tag_test.go index 83f605f3..8ec648eb 100644 --- a/models/tag_test.go +++ b/models/tag_test.go @@ -4,10 +4,10 @@ import ( "context" "testing" - "github.com/jiaozifs/jiaozifs/models" - "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" + "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/models" "github.com/jiaozifs/jiaozifs/testhelper" "github.com/stretchr/testify/require" @@ -18,14 +18,24 @@ func TestTagRepo(t *testing.T) { postgres, _, db := testhelper.SetupDatabase(ctx, t) defer postgres.Stop() //nolint - tagRepo := models.NewTagRepo(db) + repoID := uuid.New() + tagRepo := models.NewTagRepo(db, repoID) + require.Equal(t, tagRepo.RepositoryID(), repoID) tagModel := &models.Tag{} require.NoError(t, gofakeit.Struct(tagModel)) + tagModel.RepositoryID = repoID newTagModel, err := tagRepo.Insert(ctx, tagModel) require.NoError(t, err) tagModel, err = tagRepo.Tag(ctx, tagModel.Hash) require.NoError(t, err) require.True(t, cmp.Equal(tagModel, newTagModel, dbTimeCmpOpt)) + + t.Run("mis match repo id", func(t *testing.T) { + mistMatchModel := &models.Tag{} + require.NoError(t, gofakeit.Struct(mistMatchModel)) + _, err := tagRepo.Insert(ctx, mistMatchModel) + require.ErrorIs(t, err, models.ErrRepoIDMisMatch) + }) } diff --git a/models/tree.go b/models/tree.go index 921b4216..bbff7bba 100644 --- a/models/tree.go +++ b/models/tree.go @@ -3,14 +3,18 @@ package models import ( "bytes" "context" + "errors" "sort" "time" + "github.com/google/uuid" "github.com/jiaozifs/jiaozifs/models/filemode" "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/uptrace/bun" ) +var ErrRepoIDMisMatch = errors.New("repo id mismatch") + // ObjectType internal object type // Integer values from 0 to 7 map to those exposed by git. // AnyObject is used to represent any from 0 to 7. @@ -72,6 +76,7 @@ func (props Property) ToMap() map[string]string { type Blob struct { bun.BaseModel `bun:"table:trees"` Hash hash.Hash `bun:"hash,pk,type:bytea"` + RepositoryID uuid.UUID `bun:"repository_id,pk,type:uuid,notnull"` CheckSum hash.Hash `bun:"check_sum,type:bytea"` Type ObjectType `bun:"type"` Size int64 `bun:"size"` @@ -81,14 +86,15 @@ type Blob struct { UpdatedAt time.Time `bun:"updated_at"` } -func NewBlob(props Property, checkSum hash.Hash, size int64) (*Blob, error) { +func NewBlob(props Property, repoID uuid.UUID, checkSum hash.Hash, size int64) (*Blob, error) { blob := &Blob{ - CheckSum: checkSum, - Type: BlobObject, - Size: size, - Properties: props, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), + CheckSum: checkSum, + RepositoryID: repoID, + Type: BlobObject, + Size: size, + Properties: props, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), } hash, err := blob.calculateHash() if err != nil { @@ -127,34 +133,38 @@ func (blob *Blob) calculateHash() (hash.Hash, error) { func (blob *Blob) FileTree() *FileTree { return &FileTree{ - Hash: blob.Hash, - Type: blob.Type, - Size: blob.Size, - CheckSum: blob.CheckSum, - Properties: blob.Properties, - CreatedAt: blob.CreatedAt, - UpdatedAt: blob.UpdatedAt, + Hash: blob.Hash, + RepositoryID: blob.RepositoryID, + Type: blob.Type, + Size: blob.Size, + CheckSum: blob.CheckSum, + Properties: blob.Properties, + CreatedAt: blob.CreatedAt, + UpdatedAt: blob.UpdatedAt, } } type TreeNode struct { bun.BaseModel `bun:"table:trees"` - Hash hash.Hash `bun:"hash,pk,type:bytea"` - Type ObjectType `bun:"type"` - SubObjects []TreeEntry `bun:"subObjs,type:jsonb"` - Properties Property `bun:"properties,type:jsonb"` + Hash hash.Hash `bun:"hash,pk,type:bytea"` + RepositoryID uuid.UUID `bun:"repository_id,pk,type:uuid,notnull"` + + Type ObjectType `bun:"type"` + SubObjects []TreeEntry `bun:"subObjs,type:jsonb"` + Properties Property `bun:"properties,type:jsonb"` CreatedAt time.Time `bun:"created_at"` UpdatedAt time.Time `bun:"updated_at"` } -func NewTreeNode(props Property, subObjects ...TreeEntry) (*TreeNode, error) { +func NewTreeNode(props Property, repoId uuid.UUID, subObjects ...TreeEntry) (*TreeNode, error) { newTree := &TreeNode{ - Type: TreeObject, - SubObjects: SortSubObjects(subObjects), - Properties: props, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), + Type: TreeObject, + RepositoryID: repoId, + SubObjects: SortSubObjects(subObjects), + Properties: props, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), } hash, err := newTree.calculateHash() if err != nil { @@ -166,12 +176,13 @@ func NewTreeNode(props Property, subObjects ...TreeEntry) (*TreeNode, error) { func (tn *TreeNode) FileTree() *FileTree { return &FileTree{ - Hash: tn.Hash, - Type: tn.Type, - SubObjects: tn.SubObjects, - Properties: tn.Properties, - CreatedAt: tn.CreatedAt, - UpdatedAt: tn.UpdatedAt, + Hash: tn.Hash, + RepositoryID: tn.RepositoryID, + Type: tn.Type, + SubObjects: tn.SubObjects, + Properties: tn.Properties, + CreatedAt: tn.CreatedAt, + UpdatedAt: tn.UpdatedAt, } } @@ -209,6 +220,7 @@ func (tn *TreeNode) calculateHash() (hash.Hash, error) { type FileTree struct { bun.BaseModel `bun:"table:trees"` Hash hash.Hash `bun:"hash,pk,type:bytea"` + RepositoryID uuid.UUID `bun:"repository_id,pk,type:uuid,notnull"` Type ObjectType `bun:"type"` Size int64 `bun:"size"` CheckSum hash.Hash `bun:"check_sum,type:bytea"` @@ -222,24 +234,26 @@ type FileTree struct { func (fileTree *FileTree) Blob() *Blob { return &Blob{ - Hash: fileTree.Hash, - Type: fileTree.Type, - Size: fileTree.Size, - Properties: fileTree.Properties, - CheckSum: fileTree.CheckSum, - CreatedAt: fileTree.CreatedAt, - UpdatedAt: fileTree.UpdatedAt, + Hash: fileTree.Hash, + Type: fileTree.Type, + RepositoryID: fileTree.RepositoryID, + Size: fileTree.Size, + Properties: fileTree.Properties, + CheckSum: fileTree.CheckSum, + CreatedAt: fileTree.CreatedAt, + UpdatedAt: fileTree.UpdatedAt, } } func (fileTree *FileTree) TreeNode() *TreeNode { return &TreeNode{ - Hash: fileTree.Hash, - Type: fileTree.Type, - Properties: fileTree.Properties, - SubObjects: fileTree.SubObjects, - CreatedAt: fileTree.CreatedAt, - UpdatedAt: fileTree.UpdatedAt, + Hash: fileTree.Hash, + Type: fileTree.Type, + Properties: fileTree.Properties, + RepositoryID: fileTree.RepositoryID, + SubObjects: fileTree.SubObjects, + CreatedAt: fileTree.CreatedAt, + UpdatedAt: fileTree.UpdatedAt, } } @@ -257,6 +271,7 @@ func (gop *GetObjParams) SetHash(hash hash.Hash) *GetObjParams { } type IFileTreeRepo interface { + RepositoryID() uuid.UUID Insert(ctx context.Context, repo *FileTree) (*FileTree, error) Get(ctx context.Context, params *GetObjParams) (*FileTree, error) Count(ctx context.Context) (int, error) @@ -268,14 +283,25 @@ type IFileTreeRepo interface { var _ IFileTreeRepo = (*FileTreeRepo)(nil) type FileTreeRepo struct { - db bun.IDB + db bun.IDB + repositoryID uuid.UUID } -func NewFileTree(db bun.IDB) IFileTreeRepo { - return &FileTreeRepo{db: db} +func NewFileTree(db bun.IDB, repositoryID uuid.UUID) IFileTreeRepo { + return &FileTreeRepo{ + db: db, + repositoryID: repositoryID, + } +} + +func (o FileTreeRepo) RepositoryID() uuid.UUID { + return o.repositoryID } func (o FileTreeRepo) Insert(ctx context.Context, obj *FileTree) (*FileTree, error) { + if obj.RepositoryID != o.repositoryID { + return nil, ErrRepoIDMisMatch + } _, err := o.db.NewInsert().Model(obj).Ignore().Exec(ctx) if err != nil { return nil, err @@ -285,7 +311,7 @@ func (o FileTreeRepo) Insert(ctx context.Context, obj *FileTree) (*FileTree, err func (o FileTreeRepo) Get(ctx context.Context, params *GetObjParams) (*FileTree, error) { repo := &FileTree{} - query := o.db.NewSelect().Model(repo) + query := o.db.NewSelect().Model(repo).Where("repository_id = ?", o.repositoryID) if params.Hash != nil { query = query.Where("hash = ?", params.Hash) @@ -300,7 +326,11 @@ func (o FileTreeRepo) Get(ctx context.Context, params *GetObjParams) (*FileTree, func (o FileTreeRepo) Blob(ctx context.Context, hash hash.Hash) (*Blob, error) { blob := &Blob{} - err := o.db.NewSelect().Model(blob).Limit(1).Where("hash = ?", hash).Scan(ctx) + err := o.db.NewSelect(). + Model(blob).Limit(1). + Where("repository_id = ?", o.repositoryID). + Where("hash = ?", hash). + Scan(ctx) if err != nil { return nil, err } @@ -309,7 +339,11 @@ func (o FileTreeRepo) Blob(ctx context.Context, hash hash.Hash) (*Blob, error) { func (o FileTreeRepo) TreeNode(ctx context.Context, hash hash.Hash) (*TreeNode, error) { tree := &TreeNode{} - err := o.db.NewSelect().Model(tree).Limit(1).Where("hash = ?", hash).Scan(ctx) + err := o.db.NewSelect(). + Model(tree).Limit(1). + Where("repository_id = ?", o.repositoryID). + Where("hash = ?", hash). + Scan(ctx) if err != nil { return nil, err } @@ -317,12 +351,17 @@ func (o FileTreeRepo) TreeNode(ctx context.Context, hash hash.Hash) (*TreeNode, } func (o FileTreeRepo) Count(ctx context.Context) (int, error) { - return o.db.NewSelect().Model((*FileTree)(nil)).Count(ctx) + return o.db.NewSelect(). + Model((*FileTree)(nil)). + Where("repository_id = ?", o.repositoryID). + Count(ctx) } func (o FileTreeRepo) List(ctx context.Context) ([]FileTree, error) { var obj []FileTree - err := o.db.NewSelect().Model(&obj).Scan(ctx) + err := o.db.NewSelect().Model(&obj). + Where("repository_id = ?", o.repositoryID). + Scan(ctx) if err != nil { return nil, err } diff --git a/models/tree_test.go b/models/tree_test.go index e0c3846f..60cdd960 100644 --- a/models/tree_test.go +++ b/models/tree_test.go @@ -4,6 +4,8 @@ import ( "context" "testing" + "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/models/filemode" "github.com/brianvoe/gofakeit/v6" @@ -40,10 +42,13 @@ func TestObjectRepo_Insert(t *testing.T) { postgres, _, db := testhelper.SetupDatabase(ctx, t) defer postgres.Stop() //nolint - repo := models.NewFileTree(db) + repoID := uuid.New() + repo := models.NewFileTree(db, repoID) + require.Equal(t, repo.RepositoryID(), repoID) objModel := &models.FileTree{} require.NoError(t, gofakeit.Struct(objModel)) + objModel.RepositoryID = repoID objModel.Properties.Mode = filemode.Regular newObj, err := repo.Insert(ctx, objModel) require.NoError(t, err) @@ -61,4 +66,11 @@ func TestObjectRepo_Insert(t *testing.T) { require.NoError(t, err) require.True(t, cmp.Equal(newObj, ref, dbTimeCmpOpt)) + t.Run("mis match repo id", func(t *testing.T) { + mistMatchModel := &models.FileTree{} + require.NoError(t, gofakeit.Struct(mistMatchModel)) + mistMatchModel.Properties.Mode = filemode.Regular + _, err := repo.Insert(ctx, mistMatchModel) + require.ErrorIs(t, err, models.ErrRepoIDMisMatch) + }) } diff --git a/versionmgr/commit.go b/versionmgr/commit.go index 1935ed97..0367dfbd 100644 --- a/versionmgr/commit.go +++ b/versionmgr/commit.go @@ -18,21 +18,17 @@ var ( // CommitOp used to wrap some function for commit, todo not easy to use, optimize it type CommitOp struct { commit *models.Commit + repoID uuid.UUID - userRepo models.IUserRepo - fileTreeRepo models.IFileTreeRepo - commitRepo models.ICommitRepo - wipRepo models.IWipRepo + repo models.IRepo } // NewCommitOp create commit operation with repo and exit commit, if operate with new repo, set commit arguments to nil -func NewCommitOp(repo models.IRepo, commit *models.Commit) *CommitOp { +func NewCommitOp(repo models.IRepo, repoID uuid.UUID, commit *models.Commit) *CommitOp { return &CommitOp{ - commit: commit, - userRepo: repo.UserRepo(), - fileTreeRepo: repo.FileTreeRepo(), - commitRepo: repo.CommitRepo(), - wipRepo: repo.WipRepo(), + repoID: repoID, + commit: commit, //commit maybe nil + repo: repo, } } @@ -41,15 +37,25 @@ func (commitOp *CommitOp) Commit() *models.Commit { return commitOp.commit } +// Commit return commit +func (commitOp *CommitOp) CommitRepo() models.ICommitRepo { + return commitOp.repo.CommitRepo(commitOp.repoID) +} + +// Commit return commit +func (commitOp *CommitOp) FileTreeRepo() models.IFileTreeRepo { + return commitOp.repo.FileTreeRepo(commitOp.repoID) +} + // AddCommit append a new commit to current head, read changes from wip, than create a new commit with parent point to current head, // and replace tree hash with wip's currentTreeHash. func (commitOp *CommitOp) AddCommit(ctx context.Context, committer *models.User, wipID uuid.UUID, msg string) (*CommitOp, error) { - wip, err := commitOp.wipRepo.Get(ctx, models.NewGetWipParams().SetID(wipID)) + wip, err := commitOp.repo.WipRepo().Get(ctx, models.NewGetWipParams().SetID(wipID)) if err != nil { return nil, err } - creator, err := commitOp.userRepo.Get(ctx, models.NewGetUserParams().SetID(wip.CreatorID)) + creator, err := commitOp.repo.UserRepo().Get(ctx, models.NewGetUserParams().SetID(wip.CreatorID)) if err != nil { return nil, err } @@ -59,7 +65,8 @@ func (commitOp *CommitOp) AddCommit(ctx context.Context, committer *models.User, parentHash = []hash.Hash{commitOp.commit.Hash} } commit := &models.Commit{ - Hash: nil, + Hash: nil, + RepositoryID: commitOp.repoID, Author: models.Signature{ Name: creator.Name, Email: creator.Email, @@ -82,27 +89,21 @@ func (commitOp *CommitOp) AddCommit(ctx context.Context, committer *models.User, return nil, err } commit.Hash = commitHash - _, err = commitOp.commitRepo.Insert(ctx, commit) + _, err = commitOp.CommitRepo().Insert(ctx, commit) if err != nil { return nil, err } - return &CommitOp{ - commit: commit, - userRepo: commitOp.userRepo, - fileTreeRepo: commitOp.fileTreeRepo, - commitRepo: commitOp.commitRepo, - wipRepo: commitOp.wipRepo, - }, nil + return NewCommitOp(commitOp.repo, commitOp.repoID, commit), nil } // DiffCommit find file changes in two commit func (commitOp *CommitOp) DiffCommit(ctx context.Context, toCommitID hash.Hash) (*Changes, error) { - workTree, err := NewWorkTree(ctx, commitOp.fileTreeRepo, models.NewRootTreeEntry(commitOp.Commit().TreeHash)) + workTree, err := NewWorkTree(ctx, commitOp.FileTreeRepo(), models.NewRootTreeEntry(commitOp.Commit().TreeHash)) if err != nil { return nil, err } - toCommit, err := commitOp.commitRepo.Commit(ctx, toCommitID) + toCommit, err := commitOp.repo.CommitRepo(commitOp.repoID).Commit(ctx, toCommitID) if err != nil { return nil, err } @@ -112,15 +113,16 @@ func (commitOp *CommitOp) DiffCommit(ctx context.Context, toCommitID hash.Hash) // Merge implement merge like git, docs https://en.wikipedia.org/wiki/Merge_(version_control) func (commitOp *CommitOp) Merge(ctx context.Context, merger *models.User, toMergeCommitHash hash.Hash, msg string, resolver ConflictResolver) (*models.Commit, error) { + commitRepo := commitOp.CommitRepo() - toMergeCommit, err := commitOp.commitRepo.Commit(ctx, toMergeCommitHash) + toMergeCommit, err := commitOp.CommitRepo().Commit(ctx, toMergeCommitHash) if err != nil { return nil, err } //find accesstor - baseCommitNode := NewCommitNode(ctx, commitOp.Commit(), commitOp.commitRepo) - toMergeCommitNode := NewCommitNode(ctx, toMergeCommit, commitOp.commitRepo) + baseCommitNode := NewCommitNode(ctx, commitOp.Commit(), commitRepo) + toMergeCommitNode := NewCommitNode(ctx, toMergeCommit, commitRepo) { //do nothing while merge is ancestor of base @@ -161,29 +163,16 @@ func (commitOp *CommitOp) Merge(ctx context.Context, merger *models.User, toMerg bestCommit := bestAncestor[0] if len(bestAncestor) > 1 { //merge cross merge create virtual commit - firstCommit := &CommitOp{ - commit: bestAncestor[0].Commit(), - userRepo: commitOp.userRepo, - fileTreeRepo: commitOp.fileTreeRepo, - commitRepo: commitOp.commitRepo, - wipRepo: commitOp.wipRepo, - } + firstCommit := NewCommitOp(commitOp.repo, commitOp.repoID, bestAncestor[0].Commit()) virtualCommit, err := firstCommit.Merge(ctx, merger, bestAncestor[1].Commit().Hash, "", resolver) if err != nil { return nil, err } - bestCommit = NewCommitNode(ctx, virtualCommit, commitOp.commitRepo) - } - - bestCommitOp := &CommitOp{ - commit: bestAncestor[0].Commit(), - userRepo: commitOp.userRepo, - fileTreeRepo: commitOp.fileTreeRepo, - commitRepo: commitOp.commitRepo, - wipRepo: commitOp.wipRepo, + bestCommit = NewCommitNode(ctx, virtualCommit, commitRepo) } + bestCommitOp := NewCommitOp(commitOp.repo, commitOp.repoID, bestAncestor[0].Commit()) baseDiff, err := bestCommitOp.DiffCommit(ctx, commitOp.Commit().Hash) if err != nil { return nil, err @@ -195,7 +184,7 @@ func (commitOp *CommitOp) Merge(ctx context.Context, merger *models.User, toMerg } //merge diff - workTree, err := NewWorkTree(ctx, commitOp.fileTreeRepo, models.NewRootTreeEntry(bestCommit.Commit().TreeHash)) + workTree, err := NewWorkTree(ctx, commitOp.FileTreeRepo(), models.NewRootTreeEntry(bestCommit.Commit().TreeHash)) if err != nil { return nil, err } @@ -221,6 +210,7 @@ func (commitOp *CommitOp) Merge(ctx context.Context, merger *models.User, toMerg mergeCommit := &models.Commit{ Author: author, + RepositoryID: commitOp.repoID, Committer: author, MergeTag: "", Message: msg, @@ -235,7 +225,7 @@ func (commitOp *CommitOp) Merge(ctx context.Context, merger *models.User, toMerg } mergeCommit.Hash = hash - mergeCommitResult, err := commitOp.commitRepo.Insert(ctx, mergeCommit) + mergeCommitResult, err := commitRepo.Insert(ctx, mergeCommit) if err != nil { return nil, err } diff --git a/versionmgr/commit_node.go b/versionmgr/commit_node.go index 48b242f9..e6101c68 100644 --- a/versionmgr/commit_node.go +++ b/versionmgr/commit_node.go @@ -3,6 +3,7 @@ package versionmgr import ( "context" "errors" + "github.com/google/uuid" "io" "github.com/go-git/go-git/v5/plumbing/storer" @@ -33,6 +34,10 @@ func (c *CommitNode) Commit() *models.Commit { return c.commit } +func (c *CommitNode) RepoID() uuid.UUID { + return c.commit.RepositoryID +} + // TreeHash returns the TreeHash in the commit. func (c *CommitNode) TreeHash() hash.Hash { return c.commit.TreeHash diff --git a/versionmgr/commit_test.go b/versionmgr/commit_test.go index 07d7f8a6..3f34e7f6 100644 --- a/versionmgr/commit_test.go +++ b/versionmgr/commit_test.go @@ -48,12 +48,12 @@ func TestCommitOpDiffCommit(t *testing.T) { 1|b/e.txt |e1 ` - root1, err := makeRoot(ctx, repo.FileTreeRepo(), EmptyDirEntry, testData1) + root1, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), EmptyDirEntry, testData1) require.NoError(t, err) baseWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseRef.ID, EmptyRoot.Hash, root1.Hash) require.NoError(t, err) - baseCommit, err := NewCommitOp(repo, nil).AddCommit(ctx, user, baseWip.ID, "base commit") + baseCommit, err := NewCommitOp(repo, project.ID, nil).AddCommit(ctx, user, baseWip.ID, "base commit") require.NoError(t, err) require.NoError(t, rmWip(ctx, repo.WipRepo(), baseWip.ID)) @@ -63,12 +63,12 @@ func TestCommitOpDiffCommit(t *testing.T) { 3|b/e.txt |e2 1|b/g.txt |g1 ` - root2, err := makeRoot(ctx, repo.FileTreeRepo(), models.NewRootTreeEntry(root1.Hash), testData2) + root2, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(root1.Hash), testData2) require.NoError(t, err) secondWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseRef.ID, EmptyRoot.Hash, root2.Hash) require.NoError(t, err) - secondCommit, err := NewCommitOp(repo, nil).AddCommit(ctx, user, secondWip.ID, "merge commit") + secondCommit, err := NewCommitOp(repo, project.ID, nil).AddCommit(ctx, user, secondWip.ID, "merge commit") require.NoError(t, err) require.NoError(t, rmWip(ctx, repo.WipRepo(), secondWip.ID)) @@ -124,7 +124,7 @@ func TestCommitOpMerge(t *testing.T) { 1|a.txt |h1 1|b/c.txt |h2 ` - oriRoot, err := makeRoot(ctx, repo.FileTreeRepo(), EmptyDirEntry, testData) + oriRoot, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), EmptyDirEntry, testData) require.NoError(t, err) //base branch baseRef, err := makeRef(ctx, repo.RefRepo(), "feat/base", project.ID, hash.Hash("a")) @@ -133,7 +133,7 @@ func TestCommitOpMerge(t *testing.T) { oriWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseRef.ID, hash.Hash{}, oriRoot.Hash) require.NoError(t, err) - oriCommit, err := NewCommitOp(repo, nil).AddCommit(ctx, user, oriWip.ID, "") + oriCommit, err := NewCommitOp(repo, project.ID, nil).AddCommit(ctx, user, oriWip.ID, "") require.NoError(t, err) require.NoError(t, rmWip(ctx, repo.WipRepo(), oriWip.ID)) //modify a.txt @@ -142,13 +142,13 @@ func TestCommitOpMerge(t *testing.T) { 3|a.txt |h5 3|b/c.txt |h2 ` - baseModify, err := makeRoot(ctx, repo.FileTreeRepo(), models.NewRootTreeEntry(oriRoot.Hash), testData) + baseModify, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(oriRoot.Hash), testData) require.NoError(t, err) baseWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseRef.ID, oriRoot.Hash, baseModify.Hash) require.NoError(t, err) - commitA, err := NewCommitOp(repo, oriCommit.Commit()).AddCommit(ctx, user, baseWip.ID, "commit a") + commitA, err := NewCommitOp(repo, project.ID, oriCommit.Commit()).AddCommit(ctx, user, baseWip.ID, "commit a") require.NoError(t, err) require.NoError(t, rmWip(ctx, repo.WipRepo(), baseWip.ID)) @@ -162,11 +162,11 @@ func TestCommitOpMerge(t *testing.T) { 3|a.txt |h4 3|b/c.txt |h2 ` - mergeModify, err := makeRoot(ctx, repo.FileTreeRepo(), models.NewRootTreeEntry(oriRoot.Hash), testData) + mergeModify, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(oriRoot.Hash), testData) require.NoError(t, err) mergeWip, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeRef.ID, oriRoot.Hash, mergeModify.Hash) require.NoError(t, err) - commitB, err := NewCommitOp(repo, oriCommit.Commit()).AddCommit(ctx, user, mergeWip.ID, "commit b") + commitB, err := NewCommitOp(repo, project.ID, oriCommit.Commit()).AddCommit(ctx, user, mergeWip.ID, "commit b") require.NoError(t, err) require.NoError(t, rmWip(ctx, repo.WipRepo(), mergeWip.ID)) @@ -178,11 +178,11 @@ func TestCommitOpMerge(t *testing.T) { testData = ` 1|x.txt |h4 ` - rootF, err := makeRoot(ctx, repo.FileTreeRepo(), models.NewRootTreeEntry(commitAB.TreeHash), testData) + rootF, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(commitAB.TreeHash), testData) require.NoError(t, err) mergeWipF, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeRef.ID, commitAB.TreeHash, rootF.Hash) require.NoError(t, err) - commitF, err := NewCommitOp(repo, oriCommit.Commit()).AddCommit(ctx, user, mergeWipF.ID, "commit f") + commitF, err := NewCommitOp(repo, project.ID, oriCommit.Commit()).AddCommit(ctx, user, mergeWipF.ID, "commit f") require.NoError(t, err) require.NoError(t, rmWip(ctx, repo.WipRepo(), mergeWipF.ID)) @@ -196,7 +196,7 @@ func TestCommitOpMerge(t *testing.T) { 3|b/c.txt |h6 1|g/c.txt |h7 ` - modifyD, err := makeRoot(ctx, repo.FileTreeRepo(), models.NewRootTreeEntry(commitB.Commit().TreeHash), testData) + modifyD, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(commitB.Commit().TreeHash), testData) require.NoError(t, err) mergeWipD, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeRef.ID, commitB.Commit().Hash, modifyD.Hash) require.NoError(t, err) @@ -207,7 +207,7 @@ func TestCommitOpMerge(t *testing.T) { testData = ` 2|a.txt |h4 ` - modifyE, err := makeRoot(ctx, repo.FileTreeRepo(), models.NewRootTreeEntry(commitD.Commit().TreeHash), testData) + modifyE, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(commitD.Commit().TreeHash), testData) require.NoError(t, err) mergeWipE, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeRef.ID, commitD.Commit().Hash, modifyE.Hash) require.NoError(t, err) @@ -224,7 +224,7 @@ func TestCommitOpMerge(t *testing.T) { commitG, err := commitE.Merge(ctx, user, commitAB.Hash, "commit c", LeastHashResolve) require.NoError(t, err) - _, err = NewCommitOp(repo, commitC).Merge(ctx, user, commitG.Hash, "commit c", LeastHashResolve) + _, err = NewCommitOp(repo, project.ID, commitC).Merge(ctx, user, commitG.Hash, "commit c", LeastHashResolve) require.NoError(t, err) } @@ -256,7 +256,7 @@ func TestCrissCrossMerge(t *testing.T) { 1|a.txt |h1 1|b.txt |h2 ` - oriRoot, err := makeRoot(ctx, repo.FileTreeRepo(), EmptyDirEntry, testData) + oriRoot, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), EmptyDirEntry, testData) require.NoError(t, err) //base branch baseRef, err := makeRef(ctx, repo.RefRepo(), "feat/base", project.ID, hash.Hash("a")) @@ -265,7 +265,7 @@ func TestCrissCrossMerge(t *testing.T) { oriWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseRef.ID, hash.Hash{}, oriRoot.Hash) require.NoError(t, err) - oriCommit, err := NewCommitOp(repo, nil).AddCommit(ctx, user, oriWip.ID, "") + oriCommit, err := NewCommitOp(repo, project.ID, nil).AddCommit(ctx, user, oriWip.ID, "") require.NoError(t, err) require.NoError(t, rmWip(ctx, repo.WipRepo(), oriWip.ID)) @@ -274,13 +274,13 @@ func TestCrissCrossMerge(t *testing.T) { 3|a.txt |h1 3|b.txt |h3 ` - baseModify, err := makeRoot(ctx, repo.FileTreeRepo(), models.NewRootTreeEntry(oriRoot.Hash), testData) + baseModify, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(oriRoot.Hash), testData) require.NoError(t, err) baseWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseRef.ID, oriRoot.Hash, baseModify.Hash) require.NoError(t, err) - commitA, err := NewCommitOp(repo, oriCommit.Commit()).AddCommit(ctx, user, baseWip.ID, "commit a") + commitA, err := NewCommitOp(repo, project.ID, oriCommit.Commit()).AddCommit(ctx, user, baseWip.ID, "commit a") require.NoError(t, err) require.NoError(t, rmWip(ctx, repo.WipRepo(), baseWip.ID)) @@ -294,11 +294,11 @@ func TestCrissCrossMerge(t *testing.T) { 3|a.txt |h4 3|b.txt |h2 ` - mergeModify, err := makeRoot(ctx, repo.FileTreeRepo(), models.NewRootTreeEntry(oriRoot.Hash), testData) + mergeModify, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(oriRoot.Hash), testData) require.NoError(t, err) mergeWip, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeRef.ID, oriRoot.Hash, mergeModify.Hash) require.NoError(t, err) - commitB, err := NewCommitOp(repo, oriCommit.Commit()).AddCommit(ctx, user, mergeWip.ID, "commit b") + commitB, err := NewCommitOp(repo, project.ID, oriCommit.Commit()).AddCommit(ctx, user, mergeWip.ID, "commit b") require.NoError(t, err) require.NoError(t, rmWip(ctx, repo.WipRepo(), mergeWip.ID)) @@ -308,7 +308,7 @@ func TestCrissCrossMerge(t *testing.T) { commitBA, err := commitB.Merge(ctx, user, commitA.Commit().Hash, "commit ba", LeastHashResolve) require.NoError(t, err) - _, err = NewCommitOp(repo, commitAB).Merge(ctx, user, commitBA.Hash, "cross commit", LeastHashResolve) + _, err = NewCommitOp(repo, project.ID, commitAB).Merge(ctx, user, commitBA.Hash, "cross commit", LeastHashResolve) require.NoError(t, err) } @@ -409,12 +409,13 @@ func makeRoot(ctx context.Context, objRepo models.IFileTreeRepo, treeEntry model fullPath := strings.TrimSpace(commitData[1]) fileHash := strings.TrimSpace(commitData[2]) blob := &models.Blob{ - Hash: hash.Hash(fileHash), - Type: models.BlobObject, - Size: 10, - Properties: models.Property{Mode: filemode.Regular}, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), + Hash: hash.Hash(fileHash), + RepositoryID: objRepo.RepositoryID(), + Type: models.BlobObject, + Size: 10, + Properties: models.Property{Mode: filemode.Regular}, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), } if commitData[0] == "1" { diff --git a/versionmgr/merge_base_test.go b/versionmgr/merge_base_test.go index c329f083..918fc343 100644 --- a/versionmgr/merge_base_test.go +++ b/versionmgr/merge_base_test.go @@ -6,6 +6,8 @@ import ( "testing" "time" + "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/stretchr/testify/require" @@ -19,7 +21,8 @@ func TestCommitNodeMergeBase(t *testing.T) { postgres, _, db := testhelper.SetupDatabase(ctx, t) defer postgres.Stop() //nolint - commitRepo := models.NewCommitRepo(db) + repoID := uuid.New() + commitRepo := models.NewCommitRepo(db, repoID) //mock data // | -> c ------- // | | @@ -79,7 +82,7 @@ func loadCommitTestData(ctx context.Context, commitRepo models.ICommitRepo, test } commitData := strings.Split(strings.TrimSpace(line), "|") hashName := strings.TrimSpace(commitData[0]) - commit := newCommit(hashName, strings.Split(commitData[1], ",")) + commit := newCommit(commitRepo.RepositoryID(), hashName, strings.Split(commitData[1], ",")) commitMap[hashName] = NewCommitNode(ctx, commit, commitRepo) _, err := commitRepo.Insert(ctx, commit) if err != nil { @@ -89,7 +92,7 @@ func loadCommitTestData(ctx context.Context, commitRepo models.ICommitRepo, test return commitMap, nil } -func newCommit(hashStr string, parentHash []string) *models.Commit { +func newCommit(repoID uuid.UUID, hashStr string, parentHash []string) *models.Commit { var p []hash.Hash for _, pHashStr := range parentHash { pHashStr = strings.TrimSpace(pHashStr) @@ -99,8 +102,9 @@ func newCommit(hashStr string, parentHash []string) *models.Commit { p = append(p, hash.Hash(pHashStr)) } return &models.Commit{ - Hash: hash.Hash(hashStr), - Author: models.Signature{}, + Hash: hash.Hash(hashStr), + Author: models.Signature{}, + RepositoryID: repoID, Committer: models.Signature{ When: time.Now(), }, diff --git a/versionmgr/worktree.go b/versionmgr/worktree.go index 87e8524b..85e7d5a2 100644 --- a/versionmgr/worktree.go +++ b/versionmgr/worktree.go @@ -9,6 +9,8 @@ import ( "path/filepath" "strings" + "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/utils/httputil" "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" @@ -71,6 +73,9 @@ func NewWorkTree(ctx context.Context, object models.IFileTreeRepo, root models.T func (workTree *WorkTree) Root() *TreeNode { return workTree.root } +func (workTree *WorkTree) RepositoryID() uuid.UUID { + return workTree.object.RepositoryID() +} // ReadBlob read blob content with range func (workTree *WorkTree) ReadBlob(ctx context.Context, adapter block.Adapter, blob *models.Blob, rangeSpec *string) (io.ReadCloser, error) { @@ -140,7 +145,7 @@ func (workTree *WorkTree) WriteBlob(ctx context.Context, adapter block.Adapter, return nil, err } - return models.NewBlob(properties, checkSum, hashReader.CopiedSize) + return models.NewBlob(properties, workTree.RepositoryID(), checkSum, hashReader.CopiedSize) } func (workTree *WorkTree) AppendDirectEntry(ctx context.Context, treeEntry models.TreeEntry) (*models.TreeNode, error) { @@ -156,7 +161,7 @@ func (workTree *WorkTree) AppendDirectEntry(ctx context.Context, treeEntry model subObjects := models.SortSubObjects(append(workTree.root.SubObjects(), treeEntry)) - newTree, err := models.NewTreeNode(models.Property{Mode: filemode.Dir}, subObjects...) + newTree, err := models.NewTreeNode(models.Property{Mode: filemode.Dir}, workTree.RepositoryID(), subObjects...) if err != nil { return nil, err } @@ -181,7 +186,7 @@ func (workTree *WorkTree) DeleteDirectEntry(ctx context.Context, name string) (* return nil, true, nil } - newTree, err := models.NewTreeNode(workTree.root.Properties(), subObjects...) + newTree, err := models.NewTreeNode(workTree.root.Properties(), workTree.RepositoryID(), subObjects...) if err != nil { return nil, false, err } @@ -209,7 +214,7 @@ func (workTree *WorkTree) ReplaceSubTreeEntry(ctx context.Context, treeEntry mod copy(subObjects, workTree.root.SubObjects()) subObjects[index] = treeEntry - newTree, err := models.NewTreeNode(workTree.Root().Properties(), subObjects...) + newTree, err := models.NewTreeNode(workTree.Root().Properties(), workTree.RepositoryID(), subObjects...) if err != nil { return nil, err } @@ -299,7 +304,7 @@ func (workTree *WorkTree) AddLeaf(ctx context.Context, fullPath string, blob *mo continue } - newTree, err := models.NewTreeNode(models.DefaultDirProperty(), lastEntry) + newTree, err := models.NewTreeNode(models.DefaultDirProperty(), workTree.RepositoryID(), lastEntry) if err != nil { return err } diff --git a/versionmgr/worktree_test.go b/versionmgr/worktree_test.go index 247b9857..d9adacb4 100644 --- a/versionmgr/worktree_test.go +++ b/versionmgr/worktree_test.go @@ -7,6 +7,8 @@ import ( "io" "testing" + "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/stretchr/testify/assert" @@ -23,8 +25,9 @@ func TestTreeWriteBlob(t *testing.T) { postgres, _, db := testhelper.SetupDatabase(ctx, t) defer postgres.Stop() //nolint + repoID := uuid.New() adapter := mem.New(ctx) - objRepo := models.NewFileTree(db) + objRepo := models.NewFileTree(db, repoID) workTree, err := NewWorkTree(ctx, objRepo, EmptyDirEntry) require.NoError(t, err) @@ -50,8 +53,9 @@ func TestWorkTreeTreeOp(t *testing.T) { postgres, _, db := testhelper.SetupDatabase(ctx, t) defer postgres.Stop() //nolint + repoID := uuid.New() adapter := mem.New(ctx) - objRepo := models.NewFileTree(db) + objRepo := models.NewFileTree(db, repoID) workTree, err := NewWorkTree(ctx, objRepo, EmptyDirEntry) require.NoError(t, err) @@ -143,8 +147,9 @@ func TestRemoveEntry(t *testing.T) { postgres, _, db := testhelper.SetupDatabase(ctx, t) defer postgres.Stop() //nolint + repoID := uuid.New() adapter := mem.New(ctx) - objRepo := models.NewFileTree(db) + objRepo := models.NewFileTree(db, repoID) workTree, err := NewWorkTree(ctx, objRepo, EmptyDirEntry) require.NoError(t, err) From 9a019beb843661d5ce1958620fb993757cf56e11 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Wed, 20 Dec 2023 11:42:37 +0800 Subject: [PATCH 095/210] test: wip object --- controller/object_ctl.go | 12 +- integrationtest/helper.go | 2 +- integrationtest/objects_test.go | 90 +-------- integrationtest/root_test.go | 1 + integrationtest/wip_object_test.go | 294 +++++++++++++++++++++++++++++ models/tree.go | 4 +- versionmgr/commit.go | 4 +- versionmgr/commit_node.go | 5 +- 8 files changed, 310 insertions(+), 102 deletions(-) create mode 100644 integrationtest/wip_object_test.go diff --git a/controller/object_ctl.go b/controller/object_ctl.go index ece8b75c..96bff5b8 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -69,11 +69,6 @@ func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsRes w.Error(err) return } - commit, err := oct.Repo.CommitRepo(repository.ID).Commit(ctx, ref.CommitHash) - if err != nil { - w.Error(err) - return - } wip, err := oct.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetCreatorID(operator.ID).SetRepositoryID(repository.ID).SetRefID(ref.ID)) if err != nil { @@ -81,7 +76,12 @@ func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsRes return } - workTree, err := versionmgr.NewWorkTree(ctx, oct.Repo.FileTreeRepo(repository.ID), models.NewRootTreeEntry(commit.TreeHash)) + treeHash := hash.EmptyHash + if !wip.CurrentTree.IsEmpty() { + treeHash = wip.CurrentTree + } + + workTree, err := versionmgr.NewWorkTree(ctx, oct.Repo.FileTreeRepo(repository.ID), models.NewRootTreeEntry(treeHash)) if err != nil { w.Error(err) return diff --git a/integrationtest/helper.go b/integrationtest/helper.go index 8759b848..8b1a1dd2 100644 --- a/integrationtest/helper.go +++ b/integrationtest/helper.go @@ -146,7 +146,7 @@ func createRepo(ctx context.Context, c convey.C, client *api.Client, repoName st } func uploadRandomObject(ctx context.Context, c convey.C, client *api.Client, user string, repoName string, refName string, path string) { //nolint - c.Convey("upload object "+uuid.New().String(), func(c convey.C) { + c.Convey("upload object "+path, func(c convey.C) { c.Convey("success upload object", func() { resp, err := client.UploadObjectWithBody(ctx, user, repoName, &api.UploadObjectParams{ Branch: refName, diff --git a/integrationtest/objects_test.go b/integrationtest/objects_test.go index 71d8ed13..1d46ba08 100644 --- a/integrationtest/objects_test.go +++ b/integrationtest/objects_test.go @@ -182,7 +182,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) }) - c.Convey("no path", func() { + c.Convey("not exit path", func() { resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ Branch: refName, Path: "c/d.txt", @@ -260,7 +260,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) }) - c.Convey("no path", func() { + c.Convey("not exit path", func() { resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ Branch: refName, Path: "c/d.txt", @@ -287,91 +287,5 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(etag, convey.ShouldEqual, exectEtag) }) }) - - c.Convey("delete object", func(c convey.C) { - c.Convey("no auth", func() { - re := client.RequestEditors - client.RequestEditors = nil - resp, err := client.DeleteObject(ctx, userName, repoName, &api.DeleteObjectParams{ - Branch: refName, - Path: "a/b.bin", - }) - client.RequestEditors = re - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) - }) - - c.Convey("fail to delete object in non exit user", func() { - resp, err := client.DeleteObject(ctx, "mockUser", repoName, &api.DeleteObjectParams{ - Branch: refName, - Path: "a/b.bin", - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) - }) - - c.Convey("fail to delete object in non exit repo", func() { - resp, err := client.DeleteObject(ctx, userName, "fakerepo", &api.DeleteObjectParams{ - Branch: refName, - Path: "a/b.bin", - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) - }) - - c.Convey("fail to delete object in non exit branch", func() { - resp, err := client.DeleteObject(ctx, userName, repoName, &api.DeleteObjectParams{ - Branch: "mockref", - Path: "a/b.bin", - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) - }) - - c.Convey("forbidden delete object in others", func() { - resp, err := client.DeleteObject(ctx, "jimmy", "happygo", &api.DeleteObjectParams{ - Branch: "main", - Path: "a/b.bin", - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) - }) - - c.Convey("empty path", func() { - resp, err := client.DeleteObject(ctx, userName, repoName, &api.DeleteObjectParams{ - Branch: refName, - Path: "", - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) - }) - - c.Convey("no path", func() { - resp, err := client.DeleteObject(ctx, userName, repoName, &api.DeleteObjectParams{ - Branch: refName, - Path: "e/m.txt", - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) - }) - - c.Convey("success to delete object", func(c convey.C) { - resp, err := client.DeleteObject(ctx, userName, repoName, &api.DeleteObjectParams{ - Branch: refName, - Path: "a/b.bin", - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) - - commitWip(ctx, c, client, userName, repoName, refName) - - resp, err = client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ - Branch: refName, - Path: "a/b.bin", - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) - }) - }) } } diff --git a/integrationtest/root_test.go b/integrationtest/root_test.go index 2511f2a6..5ce982ca 100644 --- a/integrationtest/root_test.go +++ b/integrationtest/root_test.go @@ -17,4 +17,5 @@ func TestSpec(t *testing.T) { convey.Convey("branch test", t, BranchSpec(ctx, urlStr)) convey.Convey("branch test", t, WipSpec(ctx, urlStr)) convey.Convey("branch test", t, ObjectSpec(ctx, urlStr)) + convey.Convey("branch test", t, WipObjectSpec(ctx, urlStr)) } diff --git a/integrationtest/wip_object_test.go b/integrationtest/wip_object_test.go new file mode 100644 index 00000000..d4fa3cf0 --- /dev/null +++ b/integrationtest/wip_object_test.go @@ -0,0 +1,294 @@ +package integrationtest + +import ( + "context" + "net/http" + + "github.com/jiaozifs/jiaozifs/utils" + + "github.com/jiaozifs/jiaozifs/api" + apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" + "github.com/smartystreets/goconvey/convey" +) + +func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { + client, _ := api.NewClient(urlStr + apiimpl.APIV1Prefix) + return func(c convey.C) { + userName := "jude" + repoName := "hash" + refName := "feat/wip_obj_test" + + createUser(ctx, c, client, userName) + loginAndSwitch(ctx, c, client, userName) + createRepo(ctx, c, client, repoName) + createBranch(ctx, c, client, userName, repoName, "main", refName) + createWip(ctx, c, client, userName, repoName, refName) + uploadRandomObject(ctx, c, client, userName, repoName, refName, "m.dat") + uploadRandomObject(ctx, c, client, userName, repoName, refName, "g/m.dat") + + c.Convey("head object", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ + Branch: refName, + Path: "m.dat", + IsWip: utils.Bool(true), + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("fail to head object in non exit user", func() { + resp, err := client.HeadObject(ctx, "mock user", repoName, &api.HeadObjectParams{ + Branch: refName, + Path: "m.dat", + IsWip: utils.Bool(true), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to head object in non exit repo", func() { + resp, err := client.HeadObject(ctx, userName, "fakerepo", &api.HeadObjectParams{ + Branch: refName, + Path: "m.dat", + IsWip: utils.Bool(true), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to head object in non exit branch", func() { + resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ + Branch: "mockref", + Path: "m.dat", + IsWip: utils.Bool(true), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("forbidden head object in others", func() { + resp, err := client.HeadObject(ctx, "jimmy", "happygo", &api.HeadObjectParams{ + Branch: refName, + Path: "m.dat", + IsWip: utils.Bool(true), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + + c.Convey("empty path", func() { + resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ + Path: "", + IsWip: utils.Bool(true), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + + c.Convey("not exit path", func() { + resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ + Branch: refName, + Path: "c/d.txt", + IsWip: utils.Bool(true), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + + c.Convey("success to head object", func() { + resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ + Branch: refName, + Path: "m.dat", + IsWip: utils.Bool(true), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + }) + }) + + c.Convey("get object", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ + Branch: refName, + Path: "m.dat", + IsWip: utils.Bool(true), + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("fail to get object in non exit user", func() { + resp, err := client.GetObject(ctx, "mock user", repoName, &api.GetObjectParams{ + Branch: refName, + Path: "m.dat", + IsWip: utils.Bool(true), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to get object in non exit repo", func() { + resp, err := client.GetObject(ctx, userName, "fakerepo", &api.GetObjectParams{ + Branch: refName, + Path: "m.dat", + IsWip: utils.Bool(true), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to get object in non exit branch", func() { + resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ + Branch: "mockref", + Path: "m.dat", + IsWip: utils.Bool(true), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("forbidden get object in others", func() { + resp, err := client.GetObject(ctx, "jimmy", "happygo", &api.GetObjectParams{ + Branch: refName, + Path: "m.dat", + IsWip: utils.Bool(true), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + + c.Convey("empty path", func() { + resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ + Branch: refName, + IsWip: utils.Bool(true), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + + c.Convey("not exit path", func() { + resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ + Branch: refName, + Path: "c/d.txt", + IsWip: utils.Bool(true), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + + c.Convey("success to get object", func() { + resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ + Branch: refName, + Path: "m.dat", + IsWip: utils.Bool(true), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + }) + }) + + c.Convey("delete object", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.DeleteObject(ctx, userName, repoName, &api.DeleteObjectParams{ + Branch: refName, + Path: "g/m.dat", + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("fail to delete object in non exit user", func() { + resp, err := client.DeleteObject(ctx, "mockUser", repoName, &api.DeleteObjectParams{ + Branch: refName, + Path: "g/m.dat", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to delete object in non exit repo", func() { + resp, err := client.DeleteObject(ctx, userName, "fakerepo", &api.DeleteObjectParams{ + Branch: refName, + Path: "g/m.dat", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to delete object in non exit branch", func() { + resp, err := client.DeleteObject(ctx, userName, repoName, &api.DeleteObjectParams{ + Branch: "mockref", + Path: "g/m.dat", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("forbidden delete object in others", func() { + resp, err := client.DeleteObject(ctx, "jimmy", "happygo", &api.DeleteObjectParams{ + Branch: "main", + Path: "g/m.dat", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + + c.Convey("empty path", func() { + resp, err := client.DeleteObject(ctx, userName, repoName, &api.DeleteObjectParams{ + Branch: refName, + Path: "", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + + c.Convey("not exit path", func() { + resp, err := client.DeleteObject(ctx, userName, repoName, &api.DeleteObjectParams{ + Branch: refName, + Path: "mm/t.dat", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + + c.Convey("success to delete object", func(c convey.C) { + //ensure exit + resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ + Branch: refName, + Path: "g/m.dat", + IsWip: utils.Bool(true), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + resp, err = client.DeleteObject(ctx, userName, repoName, &api.DeleteObjectParams{ + Branch: refName, + Path: "g/m.dat", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + commitWip(ctx, c, client, userName, repoName, refName) + + //ensure not exit + resp, err = client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ + Branch: refName, + Path: "g/m.dat", + IsWip: utils.Bool(true), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + }) + } +} diff --git a/models/tree.go b/models/tree.go index bbff7bba..8c27e9ae 100644 --- a/models/tree.go +++ b/models/tree.go @@ -157,10 +157,10 @@ type TreeNode struct { UpdatedAt time.Time `bun:"updated_at"` } -func NewTreeNode(props Property, repoId uuid.UUID, subObjects ...TreeEntry) (*TreeNode, error) { +func NewTreeNode(props Property, repoID uuid.UUID, subObjects ...TreeEntry) (*TreeNode, error) { newTree := &TreeNode{ Type: TreeObject, - RepositoryID: repoId, + RepositoryID: repoID, SubObjects: SortSubObjects(subObjects), Properties: props, CreatedAt: time.Now(), diff --git a/versionmgr/commit.go b/versionmgr/commit.go index 0367dfbd..caeac473 100644 --- a/versionmgr/commit.go +++ b/versionmgr/commit.go @@ -37,12 +37,12 @@ func (commitOp *CommitOp) Commit() *models.Commit { return commitOp.commit } -// Commit return commit +// CommitRepo return commit repo func (commitOp *CommitOp) CommitRepo() models.ICommitRepo { return commitOp.repo.CommitRepo(commitOp.repoID) } -// Commit return commit +// FileTreeRepo return file tree repo func (commitOp *CommitOp) FileTreeRepo() models.IFileTreeRepo { return commitOp.repo.FileTreeRepo(commitOp.repoID) } diff --git a/versionmgr/commit_node.go b/versionmgr/commit_node.go index e6101c68..1bedabc9 100644 --- a/versionmgr/commit_node.go +++ b/versionmgr/commit_node.go @@ -3,13 +3,12 @@ package versionmgr import ( "context" "errors" - "github.com/google/uuid" "io" "github.com/go-git/go-git/v5/plumbing/storer" - "github.com/jiaozifs/jiaozifs/utils/hash" - + "github.com/google/uuid" "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/utils/hash" ) var ( From e9a55b06df008866c1ba9d4b536887d84eab1866 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Wed, 20 Dec 2023 17:51:05 +0800 Subject: [PATCH 096/210] test: add diff test --- api/jiaozifs.gen.go | 230 +++++++------ api/swagger.yml | 27 +- controller/branch_ctl.go | 24 +- controller/commit_ctl.go | 55 ++-- controller/object_ctl.go | 11 +- controller/repository_ctl.go | 2 + controller/wip_ctl.go | 18 +- integrationtest/branch_test.go | 5 +- integrationtest/commit_test.go | 304 ++++++++++++++++++ integrationtest/{helper.go => helper_test.go} | 24 +- integrationtest/objects_test.go | 6 +- integrationtest/repo_test.go | 31 +- integrationtest/root_test.go | 7 +- integrationtest/wip_object_test.go | 83 ++++- integrationtest/wip_test.go | 8 +- versionmgr/worktree.go | 25 +- versionmgr/worktree_test.go | 4 + 17 files changed, 678 insertions(+), 186 deletions(-) create mode 100644 integrationtest/commit_test.go rename integrationtest/{helper.go => helper_test.go} (84%) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index aeb132d0..3f59655c 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -29,6 +29,13 @@ const ( Jwt_tokenScopes = "jwt_token.Scopes" ) +// Defines values for ChangeAction. +const ( + N1 ChangeAction = 1 + N2 ChangeAction = 2 + N3 ChangeAction = 3 +) + // Defines values for LoginConfigRBAC. const ( External LoginConfigRBAC = "external" @@ -58,23 +65,27 @@ type BranchCreation struct { // Change defines model for Change. type Change struct { - Action int `json:"Action"` - BaseHash *string `json:"BaseHash,omitempty"` - Path string `json:"Path"` - ToHash *string `json:"ToHash,omitempty"` + Action ChangeAction `json:"Action"` + BaseHash *string `json:"BaseHash,omitempty"` + Path string `json:"Path"` + ToHash *string `json:"ToHash,omitempty"` } +// ChangeAction defines model for Change.Action. +type ChangeAction int + // Commit defines model for Commit. type Commit struct { - Author Signature `json:"Author"` - Committer Signature `json:"Committer"` - CreatedAt time.Time `json:"CreatedAt"` - Hash string `json:"Hash"` - MergeTag string `json:"MergeTag"` - Message string `json:"Message"` - ParentHashes []string `json:"ParentHashes"` - TreeHash string `json:"TreeHash"` - UpdatedAt time.Time `json:"UpdatedAt"` + Author Signature `json:"Author"` + Committer Signature `json:"Committer"` + CreatedAt time.Time `json:"CreatedAt"` + Hash string `json:"Hash"` + MergeTag string `json:"MergeTag"` + Message string `json:"Message"` + ParentHashes []string `json:"ParentHashes"` + RepositoryID openapi_types.UUID `json:"RepositoryID"` + TreeHash string `json:"TreeHash"` + UpdatedAt time.Time `json:"UpdatedAt"` } // CreateRepository defines model for CreateRepository. @@ -334,8 +345,11 @@ type GetEntriesInRefParams struct { // Path specific path, if not specific return entries in root Path *string `form:"path,omitempty" json:"path,omitempty"` - // Ref specific ref default to main branch + // Ref specific branch, default to repostiory default branch(HEAD) Ref *string `form:"ref,omitempty" json:"ref,omitempty"` + + // IsWip isWip indicate to retrieve from working in progress, default false + IsWip *bool `form:"isWip,omitempty" json:"isWip,omitempty"` } // ListRepositoryParams defines parameters for ListRepository. @@ -373,7 +387,7 @@ type GetWipChangesParams struct { // CommitWipParams defines parameters for CommitWip. type CommitWipParams struct { // Msg commit message - Msg *string `form:"msg,omitempty" json:"msg,omitempty"` + Msg string `form:"msg" json:"msg"` // RefName ref name RefName string `form:"refName" json:"refName"` @@ -1941,6 +1955,22 @@ func NewGetEntriesInRefRequest(server string, owner string, repository string, p } + if params.IsWip != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "isWip", runtime.ParamLocationQuery, *params.IsWip); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + queryURL.RawQuery = queryValues.Encode() } @@ -2484,20 +2514,16 @@ func NewCommitWipRequest(server string, owner string, repository string, params if params != nil { queryValues := queryURL.Query() - if params.Msg != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "msg", runtime.ParamLocationQuery, *params.Msg); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "msg", runtime.ParamLocationQuery, params.Msg); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) } } - } if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { @@ -3271,7 +3297,7 @@ func (r CreateWipResponse) StatusCode() int { type GetWipChangesResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *Change + JSON200 *[]Change } // Status returns HTTPResponse.Status @@ -4236,7 +4262,7 @@ func ParseGetWipChangesResponse(rsp *http.Response) (*GetWipChangesResponse, err switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest Change + var dest []Change if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -5481,6 +5507,14 @@ func (siw *ServerInterfaceWrapper) GetEntriesInRef(w http.ResponseWriter, r *htt return } + // ------------- Optional query parameter "isWip" ------------- + + err = runtime.BindQueryParameter("form", true, false, "isWip", r.URL.Query(), ¶ms.IsWip) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "isWip", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetEntriesInRef(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) @@ -5940,9 +5974,16 @@ func (siw *ServerInterfaceWrapper) CommitWip(w http.ResponseWriter, r *http.Requ // Parameter object where we will unmarshal all parameters from the context var params CommitWipParams - // ------------- Optional query parameter "msg" ------------- + // ------------- Required query parameter "msg" ------------- + + if paramValue := r.URL.Query().Get("msg"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "msg"}) + return + } - err = runtime.BindQueryParameter("form", true, false, "msg", r.URL.Query(), ¶ms.Msg) + err = runtime.BindQueryParameter("form", true, true, "msg", r.URL.Query(), ¶ms.Msg) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "msg", Err: err}) return @@ -6227,69 +6268,70 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "e04TTZcSzD+9uf93AAAA//91ZgnfjF4AAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 91ffdea6..9adebd82 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -213,6 +213,7 @@ components: - Hash - Type - CheckSum + - RepositoryID - Properties - Size - CreatedAt @@ -220,6 +221,9 @@ components: properties: Hash: type: string + RepositoryID: + type: string + format: uuid CheckSum: type: string Type: @@ -258,6 +262,7 @@ components: required: - Hash - Type + - RepositoryID - Author - Committer - MergeTag @@ -273,6 +278,9 @@ components: $ref: "#/components/schemas/Signature" Committer: $ref: "#/components/schemas/Signature" + RepositoryID: + type: string + format: uuid MergeTag: type: string Message: @@ -303,6 +311,7 @@ components: required: - Hash - Type + - RepositoryID - Properties - SubObjects - CreatedAt @@ -313,6 +322,9 @@ components: Type: type: integer format: int8 + RepositoryID: + type: string + format: uuid Properties: type: object additionalProperties: @@ -364,7 +376,7 @@ components: type: string Action: type: integer - format: int + enum: [1,2,3] BaseHash: type: string ToHash: @@ -889,7 +901,9 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Change" + type: array + items: + $ref: "#/components/schemas/Change" 400: description: ValidationError 401: @@ -924,7 +938,7 @@ paths: - in: query name: msg description: commit message - required: false + required: true schema: type: string responses: @@ -1002,10 +1016,15 @@ paths: type: string - in: query name: ref - description: specific ref default to main branch + description: specific branch, default to repostiory default branch(HEAD) required: false schema: type: string + - in: query + name: isWip + description: isWip indicate to retrieve from working in progress, default false + schema: + type: boolean responses: 200: description: commit diff --git a/controller/branch_ctl.go b/controller/branch_ctl.go index 11a4a05a..d7776706 100644 --- a/controller/branch_ctl.go +++ b/controller/branch_ctl.go @@ -18,7 +18,7 @@ import ( "go.uber.org/fx" ) -var maxBranchNameLength = 20 +var MaxBranchNameLength = 40 var branchNameRegex = regexp.MustCompile("^[a-zA-Z0-9_]*$") func CheckBranchName(name string) error { @@ -28,7 +28,7 @@ func CheckBranchName(name string) error { } } - if len(name) > maxBranchNameLength { + if len(name) > MaxBranchNameLength { return fmt.Errorf("branch name is too long") } @@ -81,8 +81,14 @@ func (bct BranchController) ListBranches(ctx context.Context, w *api.JiaozifsRes var refs []api.Ref for _, branch := range branches { ref := api.Ref{ - CommitHash: branch.Name, - Name: branch.CommitHash.Hex(), + CommitHash: branch.CommitHash.Hex(), + CreatedAt: branch.CreatedAt, + CreatorID: branch.CreatorID, + Description: branch.Description, + ID: branch.ID, + Name: branch.Name, + RepositoryID: branch.RepositoryID, + UpdatedAt: branch.UpdatedAt, } refs = append(refs, ref) } @@ -237,7 +243,13 @@ func (bct BranchController) GetBranch(ctx context.Context, w *api.JiaozifsRespon return } w.JSON(api.Ref{ - CommitHash: ref.CommitHash.Hex(), - Name: ref.Name, + CommitHash: ref.CommitHash.Hex(), + CreatedAt: ref.CreatedAt, + CreatorID: ref.CreatorID, + Description: ref.Description, + ID: ref.ID, + Name: ref.Name, + RepositoryID: ref.RepositoryID, + UpdatedAt: ref.UpdatedAt, }) } diff --git a/controller/commit_ctl.go b/controller/commit_ctl.go index 4a363ad3..c2fdd3b4 100644 --- a/controller/commit_ctl.go +++ b/controller/commit_ctl.go @@ -3,9 +3,12 @@ package controller import ( "context" "encoding/hex" + "errors" "net/http" "strings" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/jiaozifs/jiaozifs/auth" "github.com/jiaozifs/jiaozifs/utils" @@ -42,12 +45,7 @@ func (commitCtl CommitController) GetEntriesInRef(ctx context.Context, w *api.Ji return } - if operator.Name == ownerName { //todo check permission - w.Forbidden() - return - } - - refName := "main" + refName := repository.HEAD if params.Path != nil { refName = *params.Ref } @@ -58,24 +56,43 @@ func (commitCtl CommitController) GetEntriesInRef(ctx context.Context, w *api.Ji return } - commit, err := commitCtl.Repo.CommitRepo(repository.ID).Commit(ctx, ref.CommitHash) - if err != nil { - w.Error(err) + if operator.Name != ownerName { //todo check permission + w.Forbidden() return } - workTree, err := versionmgr.NewWorkTree(ctx, commitCtl.Repo.FileTreeRepo(repository.ID), models.NewRootTreeEntry(commit.TreeHash)) + treeHash := hash.EmptyHash + if utils.BoolValue(params.IsWip) { + wip, err := commitCtl.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetCreatorID(operator.ID).SetRepositoryID(repository.ID).SetRefID(ref.ID)) + if err != nil { + w.Error(err) + return + } + treeHash = wip.CurrentTree + } else { + if !ref.CommitHash.IsEmpty() { + commit, err := commitCtl.Repo.CommitRepo(repository.ID).Commit(ctx, ref.CommitHash) + if err != nil { + w.Error(err) + return + } + treeHash = commit.TreeHash + } + } + + workTree, err := versionmgr.NewWorkTree(ctx, commitCtl.Repo.FileTreeRepo(repository.ID), models.NewRootTreeEntry(treeHash)) if err != nil { w.Error(err) return } - path := "" - if params.Path != nil { - path = *params.Path - } + path := versionmgr.CleanPath(utils.StringValue(params.Path)) treeEntry, err := workTree.Ls(ctx, path) if err != nil { + if errors.Is(err, versionmgr.ErrPathNotFound) { + w.NotFound() + return + } w.Error(err) return } @@ -101,7 +118,7 @@ func (commitCtl CommitController) GetCommitDiff(ctx context.Context, w *api.Jiao return } - if operator.ID == owner.ID { //todo check permission + if operator.ID != owner.ID { //todo check permission w.Forbidden() return } @@ -129,11 +146,7 @@ func (commitCtl CommitController) GetCommitDiff(ctx context.Context, w *api.Jiao return } - path := "" - if params.Path != nil { - path = *params.Path - } - + path := versionmgr.CleanPath(utils.StringValue(params.Path)) commitOp := versionmgr.NewCommitOp(commitCtl.Repo, repository.ID, bashCommit) changes, err := commitOp.DiffCommit(ctx, toCommitHash) if err != nil { @@ -150,7 +163,7 @@ func (commitCtl CommitController) GetCommitDiff(ctx context.Context, w *api.Jiao fullPath := change.Path() if strings.HasPrefix(fullPath, path) { apiChange := api.Change{ - Action: int(action), + Action: api.ChangeAction(action), Path: fullPath, } if change.From() != nil { diff --git a/controller/object_ctl.go b/controller/object_ctl.go index 96bff5b8..09d49c94 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -87,7 +87,7 @@ func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsRes return } - err = workTree.RemoveEntry(ctx, params.Path) + err = workTree.RemoveEntry(ctx, versionmgr.CleanPath(params.Path)) if errors.Is(err, versionmgr.ErrPathNotFound) { w.BadRequest(fmt.Sprintf("path %s not found", params.Path)) return @@ -157,7 +157,7 @@ func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsRespon return } - blob, name, err := workTree.FindBlob(ctx, params.Path) + blob, name, err := workTree.FindBlob(ctx, versionmgr.CleanPath(params.Path)) if err != nil { if errors.Is(err, versionmgr.ErrPathNotFound) { w.BadRequest(fmt.Sprintf("path %s not found", params.Path)) @@ -264,7 +264,7 @@ func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsRespo return } - blob, name, err := workTree.FindBlob(ctx, params.Path) + blob, name, err := workTree.FindBlob(ctx, versionmgr.CleanPath(params.Path)) if err != nil { if errors.Is(err, versionmgr.ErrPathNotFound) { w.BadRequest(fmt.Sprintf("path %s not found", params.Path)) @@ -387,6 +387,7 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes return } + path := versionmgr.CleanPath(params.Path) var response api.ObjectStats err = oct.Repo.Transaction(ctx, func(dRepo models.IRepo) error { workingTree, err := versionmgr.NewWorkTree(ctx, dRepo.FileTreeRepo(repository.ID), models.NewRootTreeEntry(stash.CurrentTree)) @@ -400,14 +401,14 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes return err } - err = workingTree.AddLeaf(ctx, params.Path, blob) + err = workingTree.AddLeaf(ctx, path, blob) if err != nil { return err } response = api.ObjectStats{ Checksum: blob.CheckSum.Hex(), Mtime: time.Now().Unix(), - Path: params.Path, + Path: path, PathMode: utils.Uint32(uint32(filemode.Regular)), SizeBytes: swag.Int64(blob.Size), ContentType: &contentType, diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index 0a59ee64..5ae20d66 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -304,11 +304,13 @@ func (repositoryCtl RepositoryController) GetCommitsInRepository(ctx context.Con if err == nil { modelCommit := commit.Commit() commits = append(commits, api.Commit{ + RepositoryID: modelCommit.RepositoryID, Author: api.Signature{ Email: openapi_types.Email(modelCommit.Author.Email), Name: modelCommit.Author.Name, When: modelCommit.Author.When, }, + Committer: api.Signature{ Email: openapi_types.Email(modelCommit.Committer.Email), Name: modelCommit.Committer.Name, diff --git a/controller/wip_ctl.go b/controller/wip_ctl.go index dc134f7b..97659634 100644 --- a/controller/wip_ctl.go +++ b/controller/wip_ctl.go @@ -196,7 +196,7 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon return } - ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(params.RefName)) + ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(params.RefName).SetRepositoryID(repository.ID)) if err != nil { w.Error(err) return @@ -222,15 +222,10 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon } } - var msg string - if params.Msg != nil { - msg = *params.Msg - } - //add commit err = wipCtl.Repo.Transaction(ctx, func(repo models.IRepo) error { commitOp := versionmgr.NewCommitOp(repo, repository.ID, commit) - commit, err := commitOp.AddCommit(ctx, operator, wip.ID, msg) + commit, err := commitOp.AddCommit(ctx, operator, wip.ID, params.Msg) if err != nil { return err } @@ -362,10 +357,7 @@ func (wipCtl WipController) GetWipChanges(ctx context.Context, w *api.JiaozifsRe return } - var path string - if params.Path != nil { - path = *params.Path - } + path := versionmgr.CleanPath(utils.StringValue(params.Path)) var changesResp []api.Change err = changes.ForEach(func(change versionmgr.IChange) error { @@ -376,7 +368,7 @@ func (wipCtl WipController) GetWipChanges(ctx context.Context, w *api.JiaozifsRe fullPath := change.Path() if strings.HasPrefix(fullPath, path) { apiChange := api.Change{ - Action: int(action), + Action: api.ChangeAction(action), Path: fullPath, } if change.From() != nil { @@ -394,5 +386,5 @@ func (wipCtl WipController) GetWipChanges(ctx context.Context, w *api.JiaozifsRe return } - w.JSON(changes) + w.JSON(changesResp) } diff --git a/integrationtest/branch_test.go b/integrationtest/branch_test.go index fde6224d..fe44955e 100644 --- a/integrationtest/branch_test.go +++ b/integrationtest/branch_test.go @@ -3,6 +3,9 @@ package integrationtest import ( "context" "net/http" + "strings" + + "github.com/jiaozifs/jiaozifs/controller" "github.com/jiaozifs/jiaozifs/api" apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" @@ -53,7 +56,7 @@ func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("too long name", func() { resp, err := client.CreateBranch(ctx, userName, repoName, api.CreateBranchJSONRequestBody{ - Name: "feat/aaaaaaaaaaaaaaaaa", + Name: "feat/" + strings.Repeat("a", controller.MaxBranchNameLength), Source: "main", }) convey.So(err, convey.ShouldBeNil) diff --git a/integrationtest/commit_test.go b/integrationtest/commit_test.go new file mode 100644 index 00000000..5eeb8fc6 --- /dev/null +++ b/integrationtest/commit_test.go @@ -0,0 +1,304 @@ +package integrationtest + +import ( + "context" + "net/http" + + "github.com/jiaozifs/jiaozifs/utils" + + "github.com/jiaozifs/jiaozifs/api" + apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" + "github.com/smartystreets/goconvey/convey" +) + +func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { + client, _ := api.NewClient(urlStr + apiimpl.APIV1Prefix) + baseHead := utils.String("") + return func(c convey.C) { + userName := "kitty" + repoName := "black" + refName := "feat/get_entries_test" + + createUser(ctx, c, client, userName) + loginAndSwitch(ctx, c, client, userName) + createRepo(ctx, c, client, repoName) + createBranch(ctx, c, client, userName, repoName, "main", refName) + createWip(ctx, c, client, "feat get entries test0", userName, repoName, refName) + uploadObject(ctx, c, client, "update f1 to test branch", userName, repoName, refName, "m.dat") + uploadObject(ctx, c, client, "update f2 to test branch", userName, repoName, refName, "g/x.dat") + uploadObject(ctx, c, client, "update f3 to test branch", userName, repoName, refName, "g/m.dat") + + c.Convey("get wip entries", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ + Path: utils.String("g"), + Ref: utils.String(refName), + IsWip: utils.Bool(true), + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("fail to get entries in non exit user", func() { + resp, err := client.GetEntriesInRef(ctx, "mock user", repoName, &api.GetEntriesInRefParams{ + Path: utils.String("g"), + Ref: utils.String(refName), + IsWip: utils.Bool(true), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to get entries in non exit repo", func() { + resp, err := client.GetEntriesInRef(ctx, userName, "fakerepo", &api.GetEntriesInRefParams{ + Path: utils.String("g"), + Ref: utils.String(refName), + IsWip: utils.Bool(true), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to get entries in non exit branch", func() { + resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ + Path: utils.String("g"), + Ref: utils.String("feat/fake_repo"), + IsWip: utils.Bool(true), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("forbidden get entries in others", func() { + resp, err := client.GetEntriesInRef(ctx, "jimmy", "happygo", &api.GetEntriesInRefParams{ + Path: utils.String("g"), + Ref: utils.String("main"), + IsWip: utils.Bool(true), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + + c.Convey("not exit path", func() { + resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ + Path: utils.String("a/b/c/d"), + Ref: utils.String(refName), + IsWip: utils.Bool(true), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("success to get entries", func() { + resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ + Path: utils.String("g"), + Ref: utils.String(refName), + IsWip: utils.Bool(true), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseGetEntriesInRefResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(*result.JSON200, convey.ShouldHaveLength, 2) + convey.So(*(*result.JSON200)[0].Name, convey.ShouldEqual, "m.dat") + convey.So(*(*result.JSON200)[1].Name, convey.ShouldEqual, "x.dat") + }) + + c.Convey("success to get entries in root", func() { + resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ + Path: utils.String("/"), + Ref: utils.String(refName), + IsWip: utils.Bool(true), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseGetEntriesInRefResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(*result.JSON200, convey.ShouldHaveLength, 2) + convey.So(*(*result.JSON200)[0].Name, convey.ShouldEqual, "g") + convey.So(*(*result.JSON200)[1].Name, convey.ShouldEqual, "m.dat") + }) + }) + + commitWip(ctx, c, client, "commit kitty first changes", userName, repoName, refName, "test") + + c.Convey("get branch entries", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ + Path: utils.String("g"), + Ref: utils.String(refName), + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("fail to get entries in non exit user", func() { + resp, err := client.GetEntriesInRef(ctx, "mock user", repoName, &api.GetEntriesInRefParams{ + Path: utils.String("g"), + Ref: utils.String(refName), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to get entries in non exit repo", func() { + resp, err := client.GetEntriesInRef(ctx, userName, "fakerepo", &api.GetEntriesInRefParams{ + Path: utils.String("g"), + Ref: utils.String(refName), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to get entries in non exit branch", func() { + resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ + Path: utils.String("g"), + Ref: utils.String("feat/fake_repo"), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("forbidden get entries in others", func() { + resp, err := client.GetEntriesInRef(ctx, "jimmy", "happygo", &api.GetEntriesInRefParams{ + Path: utils.String("g"), + Ref: utils.String("main"), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + + c.Convey("not exit path", func() { + resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ + Path: utils.String("a/b/c/d"), + Ref: utils.String(refName), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("success to get entries", func() { + resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ + Path: utils.String("g"), + Ref: utils.String(refName), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseGetEntriesInRefResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(*result.JSON200, convey.ShouldHaveLength, 2) + convey.So(*(*result.JSON200)[0].Name, convey.ShouldEqual, "m.dat") + convey.So(*(*result.JSON200)[1].Name, convey.ShouldEqual, "x.dat") + }) + + c.Convey("success to get entries in root", func() { + resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ + Path: utils.String("/"), + Ref: utils.String(refName), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseGetEntriesInRefResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(*result.JSON200, convey.ShouldHaveLength, 2) + convey.So(*(*result.JSON200)[0].Name, convey.ShouldEqual, "g") + convey.So(*(*result.JSON200)[1].Name, convey.ShouldEqual, "m.dat") + }) + }) + + createWip(ctx, c, client, "main wip", userName, repoName, "main") + uploadObject(ctx, c, client, "update f1 to main branch", userName, repoName, "main", "a.dat") //delete\ + uploadObject(ctx, c, client, "update f2 to main branch", userName, repoName, "main", "g/m.dat") //modify + commitWip(ctx, c, client, "commit branch change", userName, repoName, "main", "test") + + c.Convey("difference", func(c convey.C) { + c.Convey("get base and head", func() { + resp, err := client.GetBranch(ctx, userName, repoName, &api.GetBranchParams{RefName: "main"}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + baseBranch, err := api.ParseGetBranchResponse(resp) + convey.So(err, convey.ShouldBeNil) + + resp, err = client.GetBranch(ctx, userName, repoName, &api.GetBranchParams{RefName: refName}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + headBranch, err := api.ParseGetBranchResponse(resp) + convey.So(err, convey.ShouldBeNil) + + baseHead = utils.String(baseBranch.JSON200.CommitHash + "..." + headBranch.JSON200.CommitHash) + }) + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.GetCommitDiff(ctx, userName, repoName, utils.StringValue(baseHead), &api.GetCommitDiffParams{ + Path: utils.String("/"), + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("fail to diff in non exit user", func() { + resp, err := client.GetCommitDiff(ctx, "mockuser", repoName, utils.StringValue(baseHead), &api.GetCommitDiffParams{ + Path: utils.String("/"), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to diff in non exit repo", func() { + resp, err := client.GetCommitDiff(ctx, userName, "fakerepo", utils.StringValue(baseHead), &api.GetCommitDiffParams{ + Path: utils.String("/"), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("forbidden diff in others", func() { + resp, err := client.GetCommitDiff(ctx, "jimmy", "happygo", utils.StringValue(baseHead), &api.GetCommitDiffParams{ + Path: utils.String("/"), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + + c.Convey("not exit path", func() { + resp, err := client.GetCommitDiff(ctx, userName, repoName, utils.StringValue(baseHead), &api.GetCommitDiffParams{ + Path: utils.String("/a/b/c/d"), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + }) + + c.Convey("success to diff", func() { + resp, err := client.GetCommitDiff(ctx, userName, repoName, utils.StringValue(baseHead), &api.GetCommitDiffParams{ + Path: utils.String("/"), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseGetCommitDiffResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(*result.JSON200, convey.ShouldHaveLength, 4) + convey.So((*result.JSON200)[0].Path, convey.ShouldEqual, "a.dat") + convey.So((*result.JSON200)[0].Action, convey.ShouldEqual, 2) + convey.So((*result.JSON200)[1].Path, convey.ShouldEqual, "g/m.dat") + convey.So((*result.JSON200)[1].Action, convey.ShouldEqual, 3) + convey.So((*result.JSON200)[2].Path, convey.ShouldEqual, "g/x.dat") + convey.So((*result.JSON200)[2].Action, convey.ShouldEqual, 1) + convey.So((*result.JSON200)[3].Path, convey.ShouldEqual, "m.dat") + convey.So((*result.JSON200)[3].Action, convey.ShouldEqual, 1) + }) + }) + } +} diff --git a/integrationtest/helper.go b/integrationtest/helper_test.go similarity index 84% rename from integrationtest/helper.go rename to integrationtest/helper_test.go index 8b1a1dd2..47bebf32 100644 --- a/integrationtest/helper.go +++ b/integrationtest/helper_test.go @@ -13,9 +13,7 @@ import ( "testing" "time" - "github.com/google/uuid" "github.com/jiaozifs/jiaozifs/api" - "github.com/jiaozifs/jiaozifs/utils" "github.com/smartystreets/goconvey/convey" "github.com/jiaozifs/jiaozifs/testhelper" @@ -145,8 +143,19 @@ func createRepo(ctx context.Context, c convey.C, client *api.Client, repoName st }) } -func uploadRandomObject(ctx context.Context, c convey.C, client *api.Client, user string, repoName string, refName string, path string) { //nolint - c.Convey("upload object "+path, func(c convey.C) { +func uploadObject(ctx context.Context, c convey.C, client *api.Client, title string, user string, repoName string, refName string, path string) { //nolint + c.Convey("upload object "+title, func(c convey.C) { + resp, err := client.UploadObjectWithBody(ctx, user, repoName, &api.UploadObjectParams{ + Branch: refName, + Path: path, + }, "application/octet-stream", io.LimitReader(rand.Reader, 50)) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + }) +} + +func deleteObject(ctx context.Context, c convey.C, client *api.Client, title string, user string, repoName string, refName string, path string) { //nolint + c.Convey("upload object "+title, func(c convey.C) { c.Convey("success upload object", func() { resp, err := client.UploadObjectWithBody(ctx, user, repoName, &api.UploadObjectParams{ Branch: refName, @@ -158,12 +167,13 @@ func uploadRandomObject(ctx context.Context, c convey.C, client *api.Client, use }) } -func commitWip(ctx context.Context, c convey.C, client *api.Client, user string, repoName string, refName string) { - c.Convey("commit wip "+uuid.New().String(), func() { +func commitWip(ctx context.Context, c convey.C, client *api.Client, title string, user string, repoName string, refName string, msg string) { + c.Convey("commit wip "+title, func() { resp, err := client.CommitWip(ctx, user, repoName, &api.CommitWipParams{ RefName: refName, - Msg: utils.String("test commit msg"), + Msg: msg, }) + convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) }) diff --git a/integrationtest/objects_test.go b/integrationtest/objects_test.go index 1d46ba08..689603ba 100644 --- a/integrationtest/objects_test.go +++ b/integrationtest/objects_test.go @@ -10,8 +10,6 @@ import ( "github.com/jiaozifs/jiaozifs/utils/hash" - "github.com/jiaozifs/jiaozifs/utils" - "github.com/jiaozifs/jiaozifs/api" apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" "github.com/smartystreets/goconvey/convey" @@ -28,7 +26,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { loginAndSwitch(ctx, c, client, userName) createRepo(ctx, c, client, repoName) createBranch(ctx, c, client, userName, repoName, "main", refName) - createWip(ctx, c, client, userName, repoName, refName) + createWip(ctx, c, client, "feat get obj test", userName, repoName, refName) c.Convey("upload object", func(c convey.C) { c.Convey("no auth", func() { @@ -119,7 +117,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("commit wip", func() { resp, err := client.CommitWip(ctx, userName, repoName, &api.CommitWipParams{ RefName: refName, - Msg: utils.String("test commit msg"), + Msg: "test commit msg", }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index be93b97e..2ae37f3e 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -263,20 +263,6 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) - - c.Convey("success get commits", func() { - resp, err := client.GetCommitsInRepository(ctx, userName, repoName, &api.GetCommitsInRepositoryParams{ - RefName: utils.String(controller.DefaultBranchName), - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) - - result, err := api.ParseGetCommitsInRepositoryResponse(resp) - convey.So(err, convey.ShouldBeNil) - convey.So(result.JSON200, convey.ShouldNotBeNil) - convey.So(len(*result.JSON200), convey.ShouldEqual, 0) - }) - c.Convey("update repository in not exit repo", func() { resp, err := client.GetCommitsInRepository(ctx, userName, "happyrunfake", &api.GetCommitsInRepositoryParams{ RefName: utils.String(controller.DefaultBranchName), @@ -299,6 +285,23 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + + }) + + createWip(ctx, c, client, "add commit to random branch", userName, repoName, controller.DefaultBranchName) + uploadObject(ctx, c, client, "add rand object", userName, repoName, controller.DefaultBranchName, "a.txt") + commitWip(ctx, c, client, "commit object", userName, repoName, controller.DefaultBranchName, "first commit") + c.Convey("success get commits", func() { + resp, err := client.GetCommitsInRepository(ctx, userName, repoName, &api.GetCommitsInRepositoryParams{ + RefName: utils.String(controller.DefaultBranchName), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseGetCommitsInRepositoryResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(*result.JSON200, convey.ShouldHaveLength, 1) + convey.So((*result.JSON200)[0].Message, convey.ShouldEqual, "first commit") }) }) diff --git a/integrationtest/root_test.go b/integrationtest/root_test.go index 5ce982ca..41017f7a 100644 --- a/integrationtest/root_test.go +++ b/integrationtest/root_test.go @@ -15,7 +15,8 @@ func TestSpec(t *testing.T) { convey.Convey("user test", t, UserSpec(ctx, urlStr)) convey.Convey("repo test", t, RepoSpec(ctx, urlStr)) convey.Convey("branch test", t, BranchSpec(ctx, urlStr)) - convey.Convey("branch test", t, WipSpec(ctx, urlStr)) - convey.Convey("branch test", t, ObjectSpec(ctx, urlStr)) - convey.Convey("branch test", t, WipObjectSpec(ctx, urlStr)) + convey.Convey("wip test", t, WipSpec(ctx, urlStr)) + convey.Convey("object test", t, ObjectSpec(ctx, urlStr)) + convey.Convey("wip object test", t, WipObjectSpec(ctx, urlStr)) + convey.Convey("commit test", t, GetEntriesInRefSpec(ctx, urlStr)) } diff --git a/integrationtest/wip_object_test.go b/integrationtest/wip_object_test.go index d4fa3cf0..cbbb12ba 100644 --- a/integrationtest/wip_object_test.go +++ b/integrationtest/wip_object_test.go @@ -22,9 +22,9 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { loginAndSwitch(ctx, c, client, userName) createRepo(ctx, c, client, repoName) createBranch(ctx, c, client, userName, repoName, "main", refName) - createWip(ctx, c, client, userName, repoName, refName) - uploadRandomObject(ctx, c, client, userName, repoName, refName, "m.dat") - uploadRandomObject(ctx, c, client, userName, repoName, refName, "g/m.dat") + createWip(ctx, c, client, "get wip obj test", userName, repoName, refName) + uploadObject(ctx, c, client, "update f1 to test branch", userName, repoName, refName, "m.dat") + uploadObject(ctx, c, client, "update f2 to test branch", userName, repoName, refName, "g/m.dat") c.Convey("head object", func(c convey.C) { c.Convey("no auth", func() { @@ -278,7 +278,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) - commitWip(ctx, c, client, userName, repoName, refName) + commitWip(ctx, c, client, "commit delete object", userName, repoName, refName, "test") //ensure not exit resp, err = client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ @@ -290,5 +290,80 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) }) }) + + uploadObject(ctx, c, client, "update f3 to test branch", userName, repoName, refName, "a/m.dat") + uploadObject(ctx, c, client, "update f4 to test branch", userName, repoName, refName, "a/b.dat") + uploadObject(ctx, c, client, "update f5 to test branch", userName, repoName, refName, "b.dat") + uploadObject(ctx, c, client, "update f6 to test branch", userName, repoName, refName, "c.dat") + + c.Convey("get wip changes", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.GetWipChanges(ctx, userName, repoName, &api.GetWipChangesParams{ + RefName: refName, + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("fail to get object in non exit user", func() { + resp, err := client.GetWipChanges(ctx, "mock user", repoName, &api.GetWipChangesParams{ + RefName: refName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to get object in non exit repo", func() { + resp, err := client.GetWipChanges(ctx, userName, "fakerepo", &api.GetWipChangesParams{ + RefName: refName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to get object in non exit branch", func() { + resp, err := client.GetWipChanges(ctx, userName, repoName, &api.GetWipChangesParams{ + RefName: "mockref", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("forbidden get object in others", func() { + resp, err := client.GetWipChanges(ctx, "jimmy", "happygo", &api.GetWipChangesParams{ + RefName: "main", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + + c.Convey("not exit path", func() { + resp, err := client.GetWipChanges(ctx, userName, repoName, &api.GetWipChangesParams{ + RefName: refName, + Path: utils.String("a/b/c/d"), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseGetWipChangesResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(*result.JSON200, convey.ShouldHaveLength, 0) + }) + + c.Convey("success to get object", func() { + resp, err := client.GetWipChanges(ctx, userName, repoName, &api.GetWipChangesParams{ + RefName: refName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseGetWipChangesResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(*result.JSON200, convey.ShouldHaveLength, 4) + }) + }) } } diff --git a/integrationtest/wip_test.go b/integrationtest/wip_test.go index e9e98014..5c8d8b65 100644 --- a/integrationtest/wip_test.go +++ b/integrationtest/wip_test.go @@ -93,7 +93,7 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { }) }) - createWip(ctx, c, client, userName, repoName, "main") + createWip(ctx, c, client, "create main wip", userName, repoName, "main") c.Convey("list wip", func(c convey.C) { c.Convey("no auth", func() { @@ -224,7 +224,7 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) }) - createWip(ctx, c, client, userName, repoName, refNameForDelete) + createWip(ctx, c, client, "creat wip for test delete", userName, repoName, refNameForDelete) c.Convey("delete branch successful", func() { //delete resp, err := client.DeleteWip(ctx, userName, repoName, &api.DeleteWipParams{RefName: refNameForDelete}) @@ -240,8 +240,8 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { } } -func createWip(ctx context.Context, c convey.C, client *api.Client, user string, repoName string, refName string) { - c.Convey("create wip "+refName, func() { +func createWip(ctx context.Context, c convey.C, client *api.Client, random string, user string, repoName string, refName string) { + c.Convey("create wip "+random, func() { resp, err := client.CreateWip(ctx, user, repoName, &api.CreateWipParams{ RefName: refName, }) diff --git a/versionmgr/worktree.go b/versionmgr/worktree.go index 85e7d5a2..21ed6022 100644 --- a/versionmgr/worktree.go +++ b/versionmgr/worktree.go @@ -226,7 +226,7 @@ func (workTree *WorkTree) ReplaceSubTreeEntry(ctx context.Context, treeEntry mod return obj.TreeNode(), nil } -func (workTree *WorkTree) MatchPath(ctx context.Context, path string) ([]FullObject, []string, error) { +func (workTree *WorkTree) matchPath(ctx context.Context, path string) ([]FullObject, []string, error) { pathSegs := strings.Split(filepath.Clean(path), fmt.Sprintf("%c", os.PathSeparator)) var existNodes []FullObject var missingPath []string @@ -274,7 +274,8 @@ func (workTree *WorkTree) MatchPath(ctx context.Context, path string) ([]FullObj // AddLeaf insert new leaf in entry, if path not exit, create new func (workTree *WorkTree) AddLeaf(ctx context.Context, fullPath string, blob *models.Blob) error { - existNode, missingPath, err := workTree.MatchPath(ctx, fullPath) + fullPath = CleanPath(fullPath) + existNode, missingPath, err := workTree.matchPath(ctx, fullPath) if err != nil { return err } @@ -351,7 +352,8 @@ func (workTree *WorkTree) AddLeaf(ctx context.Context, fullPath string, blob *mo // ReplaceLeaf replace leaf with a new blob, all parent directory updated func (workTree *WorkTree) ReplaceLeaf(ctx context.Context, fullPath string, blob *models.Blob) error { - existNode, missingPath, err := workTree.MatchPath(ctx, fullPath) + fullPath = CleanPath(fullPath) + existNode, missingPath, err := workTree.matchPath(ctx, fullPath) if err != nil { return err } @@ -413,7 +415,8 @@ func (workTree *WorkTree) ReplaceLeaf(ctx context.Context, fullPath string, blob // RemoveEntry(ctx, root, "a/b/c.txt") return new root of(a/b/c.txt) // RemoveEntry(ctx, root, "a/b") return empty root. a b c.txt d.txt all removed func (workTree *WorkTree) RemoveEntry(ctx context.Context, fullPath string) error { - existNode, missingPath, err := workTree.MatchPath(ctx, fullPath) + fullPath = CleanPath(fullPath) + existNode, missingPath, err := workTree.matchPath(ctx, fullPath) if err != nil { return err } @@ -483,11 +486,12 @@ func (workTree *WorkTree) RemoveEntry(ctx context.Context, fullPath string) erro // Ls(ctx, root, "a") return b // Ls(ctx, root, "a/b" return c.txt and d.txt func (workTree *WorkTree) Ls(ctx context.Context, fullPath string) ([]models.TreeEntry, error) { + fullPath = CleanPath(fullPath) if len(fullPath) == 0 { return workTree.root.SubObjects(), nil } - existNode, missingPath, err := workTree.MatchPath(ctx, fullPath) + existNode, missingPath, err := workTree.matchPath(ctx, fullPath) if err != nil { return nil, err } @@ -505,7 +509,8 @@ func (workTree *WorkTree) Ls(ctx context.Context, fullPath string) ([]models.Tre } func (workTree *WorkTree) FindBlob(ctx context.Context, fullPath string) (*models.Blob, string, error) { - existNode, missingPath, err := workTree.MatchPath(ctx, fullPath) + fullPath = CleanPath(fullPath) + existNode, missingPath, err := workTree.matchPath(ctx, fullPath) if err != nil { return nil, "", err } @@ -561,3 +566,11 @@ func (workTree *WorkTree) Diff(ctx context.Context, rootTreeHash hash.Hash) (*Ch } return newChanges(changes), nil } + +// CleanPath clean path +// 1. trim space +// 2. trim first or last / +// 3. to slash +func CleanPath(fullPath string) string { + return filepath.ToSlash(strings.Trim(strings.TrimSpace(fullPath), "/")) +} diff --git a/versionmgr/worktree_test.go b/versionmgr/worktree_test.go index d9adacb4..019d60a3 100644 --- a/versionmgr/worktree_test.go +++ b/versionmgr/worktree_test.go @@ -182,4 +182,8 @@ func TestRemoveEntry(t *testing.T) { entries, err := workTree.Ls(ctx, "") require.NoError(t, err) require.Len(t, entries, 0) + + entries, err = workTree.Ls(ctx, "/") + require.NoError(t, err) + require.Len(t, entries, 0) } From 30f116a8206742940f924659457d8c26d76edcbb Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Wed, 20 Dec 2023 20:04:01 +0800 Subject: [PATCH 097/210] chore: renama ref to branch --- api/jiaozifs.gen.go | 236 +++++++++--------- api/swagger.yml | 14 +- controller/branch_ctl.go | 50 ++-- controller/commit_ctl.go | 2 +- controller/object_ctl.go | 8 +- controller/repository_ctl.go | 6 +- controller/wip_ctl.go | 12 +- integrationtest/branch_test.go | 12 +- integrationtest/commit_test.go | 40 +-- integrationtest/helper_test.go | 8 +- integrationtest/objects_test.go | 102 ++++---- integrationtest/wip_object_test.go | 162 ++++++------ integrationtest/wip_test.go | 40 +-- models/{ref.go => branch.go} | 90 +++---- models/branch_test.go | 60 +++++ .../migrations/20210505110026_init_project.go | 2 +- models/ref_test.go | 60 ----- models/repo.go | 6 +- versionmgr/commit_test.go | 42 ++-- 19 files changed, 476 insertions(+), 476 deletions(-) rename models/{ref.go => branch.go} (51%) create mode 100644 models/branch_test.go delete mode 100644 models/ref_test.go diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 3f59655c..435d3fc9 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -57,12 +57,30 @@ type AuthenticationToken struct { TokenExpiration *int64 `json:"token_expiration,omitempty"` } +// Branch defines model for Branch. +type Branch struct { + CommitHash string `json:"CommitHash"` + CreatedAt time.Time `json:"CreatedAt"` + CreatorID openapi_types.UUID `json:"CreatorID"` + Description *string `json:"Description,omitempty"` + ID openapi_types.UUID `json:"ID"` + Name string `json:"Name"` + RepositoryID openapi_types.UUID `json:"RepositoryID"` + UpdatedAt time.Time `json:"UpdatedAt"` +} + // BranchCreation defines model for BranchCreation. type BranchCreation struct { Name string `json:"name"` Source string `json:"source"` } +// BranchList defines model for BranchList. +type BranchList struct { + Pagination Pagination `json:"pagination"` + Results []Branch `json:"results"` +} + // Change defines model for Change. type Change struct { Action ChangeAction `json:"Action"` @@ -157,24 +175,6 @@ type Pagination struct { Results int `json:"results"` } -// Ref defines model for Ref. -type Ref struct { - CommitHash string `json:"CommitHash"` - CreatedAt time.Time `json:"CreatedAt"` - CreatorID openapi_types.UUID `json:"CreatorID"` - Description *string `json:"Description,omitempty"` - ID openapi_types.UUID `json:"ID"` - Name string `json:"Name"` - RepositoryID openapi_types.UUID `json:"RepositoryID"` - UpdatedAt time.Time `json:"UpdatedAt"` -} - -// RefList defines model for RefList. -type RefList struct { - Pagination Pagination `json:"pagination"` - Results []Ref `json:"results"` -} - // Repository defines model for Repository. type Repository struct { CreatedAt time.Time `json:"CreatedAt"` @@ -266,8 +266,8 @@ type LoginJSONBody struct { // DeleteObjectParams defines parameters for DeleteObject. type DeleteObjectParams struct { - // Branch branch to the ref - Branch string `form:"branch" json:"branch"` + // RefName branch/tag to the ref + RefName string `form:"refName" json:"refName"` // Path relative to the ref Path string `form:"path" json:"path"` @@ -278,8 +278,8 @@ type GetObjectParams struct { // IsWip isWip indicate to retrieve from working in progress, default false IsWip *bool `form:"isWip,omitempty" json:"isWip,omitempty"` - // Branch branch to the ref - Branch string `form:"branch" json:"branch"` + // RefName branch/tag to the ref + RefName string `form:"refName" json:"refName"` // Path relative to the ref Path string `form:"path" json:"path"` @@ -293,8 +293,8 @@ type HeadObjectParams struct { // IsWip isWip indicate to retrieve from working in progress, default false IsWip *bool `form:"isWip,omitempty" json:"isWip,omitempty"` - // Branch branch to the ref - Branch string `form:"branch" json:"branch"` + // RefName branch/tag to the ref + RefName string `form:"refName" json:"refName"` // Path relative to the ref Path string `form:"path" json:"path"` @@ -311,8 +311,8 @@ type UploadObjectMultipartBody struct { // UploadObjectParams defines parameters for UploadObject. type UploadObjectParams struct { - // Branch branch to the ref - Branch string `form:"branch" json:"branch"` + // RefName branch/tag to the ref + RefName string `form:"refName" json:"refName"` // Path relative to the ref Path string `form:"path" json:"path"` @@ -1093,7 +1093,7 @@ func NewDeleteObjectRequest(server string, owner string, repository string, para if params != nil { queryValues := queryURL.Query() - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "branch", runtime.ParamLocationQuery, params.Branch); err != nil { + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -1180,7 +1180,7 @@ func NewGetObjectRequest(server string, owner string, repository string, params } - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "branch", runtime.ParamLocationQuery, params.Branch); err != nil { + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -1282,7 +1282,7 @@ func NewHeadObjectRequest(server string, owner string, repository string, params } - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "branch", runtime.ParamLocationQuery, params.Branch); err != nil { + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -1368,7 +1368,7 @@ func NewUploadObjectRequestWithBody(server string, owner string, repository stri if params != nil { queryValues := queryURL.Query() - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "branch", runtime.ParamLocationQuery, params.Branch); err != nil { + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -2947,7 +2947,7 @@ func (r DeleteBranchResponse) StatusCode() int { type GetBranchResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *Ref + JSON200 *Branch } // Status returns HTTPResponse.Status @@ -2991,7 +2991,7 @@ func (r CreateBranchResponse) StatusCode() int { type ListBranchesResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *RefList + JSON200 *BranchList } // Status returns HTTPResponse.Status @@ -3866,7 +3866,7 @@ func ParseGetBranchResponse(rsp *http.Response) (*GetBranchResponse, error) { switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest Ref + var dest Branch if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -3918,7 +3918,7 @@ func ParseListBranchesResponse(rsp *http.Response) (*ListBranchesResponse, error switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest RefList + var dest BranchList if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -4681,18 +4681,18 @@ func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.R // Parameter object where we will unmarshal all parameters from the context var params DeleteObjectParams - // ------------- Required query parameter "branch" ------------- + // ------------- Required query parameter "refName" ------------- - if paramValue := r.URL.Query().Get("branch"); paramValue != "" { + if paramValue := r.URL.Query().Get("refName"); paramValue != "" { } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "branch"}) + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) return } - err = runtime.BindQueryParameter("form", true, true, "branch", r.URL.Query(), ¶ms.Branch) + err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "branch", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) return } @@ -4763,18 +4763,18 @@ func (siw *ServerInterfaceWrapper) GetObject(w http.ResponseWriter, r *http.Requ return } - // ------------- Required query parameter "branch" ------------- + // ------------- Required query parameter "refName" ------------- - if paramValue := r.URL.Query().Get("branch"); paramValue != "" { + if paramValue := r.URL.Query().Get("refName"); paramValue != "" { } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "branch"}) + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) return } - err = runtime.BindQueryParameter("form", true, true, "branch", r.URL.Query(), ¶ms.Branch) + err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "branch", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) return } @@ -4866,18 +4866,18 @@ func (siw *ServerInterfaceWrapper) HeadObject(w http.ResponseWriter, r *http.Req return } - // ------------- Required query parameter "branch" ------------- + // ------------- Required query parameter "refName" ------------- - if paramValue := r.URL.Query().Get("branch"); paramValue != "" { + if paramValue := r.URL.Query().Get("refName"); paramValue != "" { } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "branch"}) + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) return } - err = runtime.BindQueryParameter("form", true, true, "branch", r.URL.Query(), ¶ms.Branch) + err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "branch", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) return } @@ -4961,18 +4961,18 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R // Parameter object where we will unmarshal all parameters from the context var params UploadObjectParams - // ------------- Required query parameter "branch" ------------- + // ------------- Required query parameter "refName" ------------- - if paramValue := r.URL.Query().Get("branch"); paramValue != "" { + if paramValue := r.URL.Query().Get("refName"); paramValue != "" { } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "branch"}) + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) return } - err = runtime.BindQueryParameter("form", true, true, "branch", r.URL.Query(), ¶ms.Branch) + err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "branch", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) return } @@ -6268,70 +6268,70 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "e04TTZcSzD+9uf93AAAA//91ZgnfjF4AAA==", + "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", + "HQAA//+t9VIHoF4AAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 9adebd82..21503ea6 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -121,7 +121,7 @@ components: type: string source: type: string - Ref: + Branch: type: object required: - ID @@ -153,7 +153,7 @@ components: UpdatedAt: type: string format: date-time - RefList: + BranchList: type: object required: - pagination @@ -164,7 +164,7 @@ components: results: type: array items: - $ref: "#/components/schemas/Ref" + $ref: "#/components/schemas/Branch" CreateRepository: type: object required: @@ -590,8 +590,8 @@ paths: schema: type: string - in: query - name: branch - description: branch to the ref + name: refName + description: branch/tag to the ref required: true schema: type: string @@ -1292,7 +1292,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/RefList" + $ref: "#/components/schemas/BranchList" 401: description: Unauthorized 404: @@ -1332,7 +1332,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Ref" + $ref: "#/components/schemas/Branch" 401: description: Unauthorized 404: diff --git a/controller/branch_ctl.go b/controller/branch_ctl.go index d7776706..f697cfbc 100644 --- a/controller/branch_ctl.go +++ b/controller/branch_ctl.go @@ -34,7 +34,7 @@ func CheckBranchName(name string) error { seg := strings.Split(name, "/") if len(seg) > 2 { - return fmt.Errorf("ref format must be or /") + return fmt.Errorf("branch format must be or /") } if !branchNameRegex.Match([]byte(seg[0])) || !branchNameRegex.Match([]byte(seg[1])) { @@ -73,14 +73,14 @@ func (bct BranchController) ListBranches(ctx context.Context, w *api.JiaozifsRes return } - branches, err := bct.Repo.RefRepo().List(ctx, models.NewListRefParams().SetRepositoryID(repository.ID)) + branches, err := bct.Repo.BranchRepo().List(ctx, models.NewListBranchParams().SetRepositoryID(repository.ID)) if err != nil { w.Error(err) return } - var refs []api.Ref + var apiBranches []api.Branch for _, branch := range branches { - ref := api.Ref{ + branch := api.Branch{ CommitHash: branch.CommitHash.Hex(), CreatedAt: branch.CreatedAt, CreatorID: branch.CreatorID, @@ -90,9 +90,9 @@ func (bct BranchController) ListBranches(ctx context.Context, w *api.JiaozifsRes RepositoryID: branch.RepositoryID, UpdatedAt: branch.UpdatedAt, } - refs = append(refs, ref) + apiBranches = append(apiBranches, branch) } - w.JSON(api.RefList{Results: refs}) + w.JSON(api.BranchList{Results: apiBranches}) } func (bct BranchController) CreateBranch(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.CreateBranchJSONRequestBody, ownerName string, repositoryName string) { @@ -126,7 +126,7 @@ func (bct BranchController) CreateBranch(ctx context.Context, w *api.JiaozifsRes } //check exit - _, err = bct.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(body.Name).SetRepositoryID(repository.ID)) + _, err = bct.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetName(body.Name).SetRepositoryID(repository.ID)) if err == nil { w.BadRequest(fmt.Sprintf("%s already exit", body.Name)) return @@ -135,20 +135,20 @@ func (bct BranchController) CreateBranch(ctx context.Context, w *api.JiaozifsRes w.Error(err) return } - //get source ref - sourceRef, err := bct.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(body.Source).SetRepositoryID(repository.ID)) + //get source branch + sourceBranch, err := bct.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetName(body.Source).SetRepositoryID(repository.ID)) if err != nil && !errors.Is(err, models.ErrNotFound) { w.Error(err) return } commitHash := hash.EmptyHash - if sourceRef != nil { - commitHash = sourceRef.CommitHash + if sourceBranch != nil { + commitHash = sourceBranch.CommitHash } // Create branch - newRef := &models.Ref{ + newBranch := &models.Branches{ RepositoryID: repository.ID, CommitHash: commitHash, Name: body.Name, @@ -156,20 +156,20 @@ func (bct BranchController) CreateBranch(ctx context.Context, w *api.JiaozifsRes CreatedAt: time.Now(), UpdatedAt: time.Now(), } - newRef, err = bct.Repo.RefRepo().Insert(ctx, newRef) + newBranch, err = bct.Repo.BranchRepo().Insert(ctx, newBranch) if err != nil { w.Error(err) return } - w.JSON(api.Ref{ - CommitHash: newRef.CommitHash.Hex(), - CreatedAt: newRef.CreatedAt, - CreatorID: newRef.CreatorID, - Description: newRef.Description, - ID: newRef.ID, - Name: newRef.Name, - RepositoryID: newRef.RepositoryID, - UpdatedAt: newRef.UpdatedAt, + w.JSON(api.Branch{ + CommitHash: newBranch.CommitHash.Hex(), + CreatedAt: newBranch.CreatedAt, + CreatorID: newBranch.CreatorID, + Description: newBranch.Description, + ID: newBranch.ID, + Name: newBranch.Name, + RepositoryID: newBranch.RepositoryID, + UpdatedAt: newBranch.UpdatedAt, }, http.StatusCreated) } @@ -199,7 +199,7 @@ func (bct BranchController) DeleteBranch(ctx context.Context, w *api.JiaozifsRes } // Delete branch - affectedRows, err := bct.Repo.RefRepo().Delete(ctx, models.NewDeleteRefParams().SetName(params.RefName).SetRepositoryID(repository.ID)) + affectedRows, err := bct.Repo.BranchRepo().Delete(ctx, models.NewDeleteBranchParams().SetName(params.RefName).SetRepositoryID(repository.ID)) if err != nil { w.Error(err) return @@ -237,12 +237,12 @@ func (bct BranchController) GetBranch(ctx context.Context, w *api.JiaozifsRespon } // Get branch - ref, err := bct.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(params.RefName).SetRepositoryID(repository.ID)) + ref, err := bct.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetName(params.RefName).SetRepositoryID(repository.ID)) if err != nil { w.Error(err) return } - w.JSON(api.Ref{ + w.JSON(api.Branch{ CommitHash: ref.CommitHash.Hex(), CreatedAt: ref.CreatedAt, CreatorID: ref.CreatorID, diff --git a/controller/commit_ctl.go b/controller/commit_ctl.go index c2fdd3b4..3670376d 100644 --- a/controller/commit_ctl.go +++ b/controller/commit_ctl.go @@ -50,7 +50,7 @@ func (commitCtl CommitController) GetEntriesInRef(ctx context.Context, w *api.Ji refName = *params.Ref } - ref, err := commitCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetRepositoryID(repository.ID).SetName(refName)) + ref, err := commitCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(refName)) if err != nil { w.Error(err) return diff --git a/controller/object_ctl.go b/controller/object_ctl.go index 09d49c94..2ebe598c 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -64,7 +64,7 @@ func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsRes return } - ref, err := oct.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetRepositoryID(repository.ID).SetName(params.Branch)) + ref, err := oct.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(params.RefName)) if err != nil { w.Error(err) return @@ -125,7 +125,7 @@ func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsRespon return } - ref, err := oct.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetRepositoryID(repository.ID).SetName(params.Branch)) + ref, err := oct.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(params.RefName)) if err != nil { w.Error(err) return @@ -231,7 +231,7 @@ func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsRespo w.Error(err) return } - ref, err := oct.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetRepositoryID(repository.ID).SetName(params.Branch)) + ref, err := oct.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(params.RefName)) if err != nil { w.Error(err) return @@ -369,7 +369,7 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes return } - ref, err := oct.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(params.Branch).SetRepositoryID(repository.ID)) + ref, err := oct.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetName(params.RefName).SetRepositoryID(repository.ID)) if err != nil { w.Error(err) return diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index 5ae20d66..7d3b8d30 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -114,7 +114,7 @@ func (repositoryCtl RepositoryController) CreateRepository(ctx context.Context, var createdRepo *models.Repository err = repositoryCtl.Repo.Transaction(ctx, func(repo models.IRepo) error { repoID := uuid.New() - defaultRef := &models.Ref{ + defaultRef := &models.Branches{ RepositoryID: repoID, CommitHash: hash.Hash{}, Name: DefaultBranchName, @@ -122,7 +122,7 @@ func (repositoryCtl RepositoryController) CreateRepository(ctx context.Context, CreatedAt: time.Now(), UpdatedAt: time.Now(), } - defaultRef, err := repositoryCtl.Repo.RefRepo().Insert(ctx, defaultRef) + defaultRef, err := repositoryCtl.Repo.BranchRepo().Insert(ctx, defaultRef) if err != nil { return err } @@ -279,7 +279,7 @@ func (repositoryCtl RepositoryController) GetCommitsInRepository(ctx context.Con if params.RefName != nil { refName = *params.RefName } - ref, err := repositoryCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetRepositoryID(repository.ID).SetName(refName)) + ref, err := repositoryCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(refName)) if err != nil { w.Error(err) return diff --git a/controller/wip_ctl.go b/controller/wip_ctl.go index 97659634..869a8d90 100644 --- a/controller/wip_ctl.go +++ b/controller/wip_ctl.go @@ -53,7 +53,7 @@ func (wipCtl WipController) CreateWip(ctx context.Context, w *api.JiaozifsRespon return } - ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetRepositoryID(repository.ID).SetName(params.RefName)) + ref, err := wipCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(params.RefName)) if err != nil { w.Error(err) return @@ -122,7 +122,7 @@ func (wipCtl WipController) GetWip(ctx context.Context, w *api.JiaozifsResponse, return } - ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetRepositoryID(repository.ID).SetName(params.RefName)) + ref, err := wipCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(params.RefName)) if err != nil { w.Error(err) return @@ -196,7 +196,7 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon return } - ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetName(params.RefName).SetRepositoryID(repository.ID)) + ref, err := wipCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetName(params.RefName).SetRepositoryID(repository.ID)) if err != nil { w.Error(err) return @@ -237,7 +237,7 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon return err } - return repo.RefRepo().UpdateByID(ctx, models.NewUpdateRefParams(ref.ID).SetCommitHash(commit.Commit().Hash)) + return repo.BranchRepo().UpdateByID(ctx, models.NewUpdateBranchParams(ref.ID).SetCommitHash(commit.Commit().Hash)) }) if err != nil { w.Error(err) @@ -272,7 +272,7 @@ func (wipCtl WipController) DeleteWip(ctx context.Context, w *api.JiaozifsRespon return } - ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetRepositoryID(repository.ID).SetName(params.RefName)) + ref, err := wipCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(params.RefName)) if err != nil { w.Error(err) return @@ -322,7 +322,7 @@ func (wipCtl WipController) GetWipChanges(ctx context.Context, w *api.JiaozifsRe return } - ref, err := wipCtl.Repo.RefRepo().Get(ctx, models.NewGetRefParams().SetRepositoryID(repository.ID).SetName(params.RefName)) + ref, err := wipCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(params.RefName)) if err != nil { w.Error(err) return diff --git a/integrationtest/branch_test.go b/integrationtest/branch_test.go index fe44955e..660c670c 100644 --- a/integrationtest/branch_test.go +++ b/integrationtest/branch_test.go @@ -17,7 +17,7 @@ func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { return func(c convey.C) { userName := "mike" repoName := "mlops" - refName := "feat/test" + branchName := "feat/test" createUser(ctx, c, client, userName) loginAndSwitch(ctx, c, client, userName) @@ -38,7 +38,7 @@ func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("success create branch", func() { resp, err := client.CreateBranch(ctx, userName, repoName, api.CreateBranchJSONRequestBody{ - Name: refName, + Name: branchName, Source: "main", }) convey.So(err, convey.ShouldBeNil) @@ -96,7 +96,7 @@ func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { re := client.RequestEditors client.RequestEditors = nil resp, err := client.GetBranch(ctx, userName, repoName, &api.GetBranchParams{ - RefName: refName, + RefName: branchName, }) client.RequestEditors = re convey.So(err, convey.ShouldBeNil) @@ -105,14 +105,14 @@ func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("success get branch", func() { resp, err := client.GetBranch(ctx, userName, repoName, &api.GetBranchParams{ - RefName: refName, + RefName: branchName, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) respResult, err := api.ParseGetBranchResponse(resp) convey.So(err, convey.ShouldBeNil) - convey.So(respResult.JSON200.Name, convey.ShouldEqual, refName) + convey.So(respResult.JSON200.Name, convey.ShouldEqual, branchName) }) c.Convey("fail to get non exit ref", func() { @@ -139,7 +139,7 @@ func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) }) - c.Convey("fail to others ref", func() { + c.Convey("fail to others branch", func() { resp, err := client.GetBranch(ctx, "jimmy", "happygo", &api.GetBranchParams{ RefName: "main", }) diff --git a/integrationtest/commit_test.go b/integrationtest/commit_test.go index 5eeb8fc6..0720c0c8 100644 --- a/integrationtest/commit_test.go +++ b/integrationtest/commit_test.go @@ -17,16 +17,16 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { return func(c convey.C) { userName := "kitty" repoName := "black" - refName := "feat/get_entries_test" + branchName := "feat/get_entries_test" createUser(ctx, c, client, userName) loginAndSwitch(ctx, c, client, userName) createRepo(ctx, c, client, repoName) - createBranch(ctx, c, client, userName, repoName, "main", refName) - createWip(ctx, c, client, "feat get entries test0", userName, repoName, refName) - uploadObject(ctx, c, client, "update f1 to test branch", userName, repoName, refName, "m.dat") - uploadObject(ctx, c, client, "update f2 to test branch", userName, repoName, refName, "g/x.dat") - uploadObject(ctx, c, client, "update f3 to test branch", userName, repoName, refName, "g/m.dat") + createBranch(ctx, c, client, userName, repoName, "main", branchName) + createWip(ctx, c, client, "feat get entries test0", userName, repoName, branchName) + uploadObject(ctx, c, client, "update f1 to test branch", userName, repoName, branchName, "m.dat") + uploadObject(ctx, c, client, "update f2 to test branch", userName, repoName, branchName, "g/x.dat") + uploadObject(ctx, c, client, "update f3 to test branch", userName, repoName, branchName, "g/m.dat") c.Convey("get wip entries", func(c convey.C) { c.Convey("no auth", func() { @@ -34,7 +34,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { client.RequestEditors = nil resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ Path: utils.String("g"), - Ref: utils.String(refName), + Ref: utils.String(branchName), IsWip: utils.Bool(true), }) client.RequestEditors = re @@ -45,7 +45,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to get entries in non exit user", func() { resp, err := client.GetEntriesInRef(ctx, "mock user", repoName, &api.GetEntriesInRefParams{ Path: utils.String("g"), - Ref: utils.String(refName), + Ref: utils.String(branchName), IsWip: utils.Bool(true), }) convey.So(err, convey.ShouldBeNil) @@ -55,7 +55,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to get entries in non exit repo", func() { resp, err := client.GetEntriesInRef(ctx, userName, "fakerepo", &api.GetEntriesInRefParams{ Path: utils.String("g"), - Ref: utils.String(refName), + Ref: utils.String(branchName), IsWip: utils.Bool(true), }) convey.So(err, convey.ShouldBeNil) @@ -85,7 +85,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("not exit path", func() { resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ Path: utils.String("a/b/c/d"), - Ref: utils.String(refName), + Ref: utils.String(branchName), IsWip: utils.Bool(true), }) convey.So(err, convey.ShouldBeNil) @@ -95,7 +95,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("success to get entries", func() { resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ Path: utils.String("g"), - Ref: utils.String(refName), + Ref: utils.String(branchName), IsWip: utils.Bool(true), }) convey.So(err, convey.ShouldBeNil) @@ -111,7 +111,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("success to get entries in root", func() { resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ Path: utils.String("/"), - Ref: utils.String(refName), + Ref: utils.String(branchName), IsWip: utils.Bool(true), }) convey.So(err, convey.ShouldBeNil) @@ -125,7 +125,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { }) }) - commitWip(ctx, c, client, "commit kitty first changes", userName, repoName, refName, "test") + commitWip(ctx, c, client, "commit kitty first changes", userName, repoName, branchName, "test") c.Convey("get branch entries", func(c convey.C) { c.Convey("no auth", func() { @@ -133,7 +133,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { client.RequestEditors = nil resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ Path: utils.String("g"), - Ref: utils.String(refName), + Ref: utils.String(branchName), }) client.RequestEditors = re convey.So(err, convey.ShouldBeNil) @@ -143,7 +143,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to get entries in non exit user", func() { resp, err := client.GetEntriesInRef(ctx, "mock user", repoName, &api.GetEntriesInRefParams{ Path: utils.String("g"), - Ref: utils.String(refName), + Ref: utils.String(branchName), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -152,7 +152,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to get entries in non exit repo", func() { resp, err := client.GetEntriesInRef(ctx, userName, "fakerepo", &api.GetEntriesInRefParams{ Path: utils.String("g"), - Ref: utils.String(refName), + Ref: utils.String(branchName), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -179,7 +179,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("not exit path", func() { resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ Path: utils.String("a/b/c/d"), - Ref: utils.String(refName), + Ref: utils.String(branchName), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -188,7 +188,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("success to get entries", func() { resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ Path: utils.String("g"), - Ref: utils.String(refName), + Ref: utils.String(branchName), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) @@ -203,7 +203,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("success to get entries in root", func() { resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ Path: utils.String("/"), - Ref: utils.String(refName), + Ref: utils.String(branchName), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) @@ -229,7 +229,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { baseBranch, err := api.ParseGetBranchResponse(resp) convey.So(err, convey.ShouldBeNil) - resp, err = client.GetBranch(ctx, userName, repoName, &api.GetBranchParams{RefName: refName}) + resp, err = client.GetBranch(ctx, userName, repoName, &api.GetBranchParams{RefName: branchName}) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) headBranch, err := api.ParseGetBranchResponse(resp) diff --git a/integrationtest/helper_test.go b/integrationtest/helper_test.go index 47bebf32..e40030a7 100644 --- a/integrationtest/helper_test.go +++ b/integrationtest/helper_test.go @@ -146,8 +146,8 @@ func createRepo(ctx context.Context, c convey.C, client *api.Client, repoName st func uploadObject(ctx context.Context, c convey.C, client *api.Client, title string, user string, repoName string, refName string, path string) { //nolint c.Convey("upload object "+title, func(c convey.C) { resp, err := client.UploadObjectWithBody(ctx, user, repoName, &api.UploadObjectParams{ - Branch: refName, - Path: path, + RefName: refName, + Path: path, }, "application/octet-stream", io.LimitReader(rand.Reader, 50)) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) @@ -158,8 +158,8 @@ func deleteObject(ctx context.Context, c convey.C, client *api.Client, title str c.Convey("upload object "+title, func(c convey.C) { c.Convey("success upload object", func() { resp, err := client.UploadObjectWithBody(ctx, user, repoName, &api.UploadObjectParams{ - Branch: refName, - Path: path, + RefName: refName, + Path: path, }, "application/octet-stream", io.LimitReader(rand.Reader, 50)) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) diff --git a/integrationtest/objects_test.go b/integrationtest/objects_test.go index 689603ba..763023bd 100644 --- a/integrationtest/objects_test.go +++ b/integrationtest/objects_test.go @@ -20,21 +20,21 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { return func(c convey.C) { userName := "molly" repoName := "dataspace" - refName := "feat/obj_test" + branchName := "feat/obj_test" createUser(ctx, c, client, userName) loginAndSwitch(ctx, c, client, userName) createRepo(ctx, c, client, repoName) - createBranch(ctx, c, client, userName, repoName, "main", refName) - createWip(ctx, c, client, "feat get obj test", userName, repoName, refName) + createBranch(ctx, c, client, userName, repoName, "main", branchName) + createWip(ctx, c, client, "feat get obj test", userName, repoName, branchName) c.Convey("upload object", func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors client.RequestEditors = nil resp, err := client.UploadObjectWithBody(ctx, userName, repoName, &api.UploadObjectParams{ - Branch: refName, - Path: "a.bin", + RefName: branchName, + Path: "a.bin", }, "application/octet-stream", bytes.NewReader([]byte{1, 2, 3, 4, 5, 6, 7, 8})) client.RequestEditors = re convey.So(err, convey.ShouldBeNil) @@ -43,8 +43,8 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to create branch in non exit user", func() { resp, err := client.UploadObjectWithBody(ctx, "mockuser", "main", &api.UploadObjectParams{ - Branch: refName, - Path: "a.bin", + RefName: branchName, + Path: "a.bin", }, "application/octet-stream", bytes.NewReader([]byte{1, 2, 3, 4, 5, 6, 7, 8})) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -52,8 +52,8 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to upload in non exit repo", func() { resp, err := client.UploadObjectWithBody(ctx, userName, "fakerepo", &api.UploadObjectParams{ - Branch: refName, - Path: "a.bin", + RefName: branchName, + Path: "a.bin", }, "application/octet-stream", bytes.NewReader([]byte{1, 2, 3, 4, 5, 6, 7, 8})) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -61,8 +61,8 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to upload object in non exit branch", func() { resp, err := client.UploadObjectWithBody(ctx, userName, repoName, &api.UploadObjectParams{ - Branch: "mockref", - Path: "a.bin", + RefName: "mockref", + Path: "a.bin", }, "application/octet-stream", bytes.NewReader([]byte{1, 2, 3, 4, 5, 6, 7, 8})) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -70,8 +70,8 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to upload object in no wip ", func() { resp, err := client.UploadObjectWithBody(ctx, userName, repoName, &api.UploadObjectParams{ - Branch: "main", - Path: "a.bin", + RefName: "main", + Path: "a.bin", }, "application/octet-stream", bytes.NewReader([]byte{1, 2, 3, 4, 5, 6, 7, 8})) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -79,8 +79,8 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("forbidden upload object in others", func() { resp, err := client.UploadObjectWithBody(ctx, "jimmy", "happygo", &api.UploadObjectParams{ - Branch: "main", - Path: "a.bin", + RefName: "main", + Path: "a.bin", }, "application/octet-stream", bytes.NewReader([]byte{1, 2, 3, 4, 5, 6, 7, 8})) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) @@ -88,7 +88,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("empty path", func() { resp, err := client.UploadObjectWithBody(ctx, userName, repoName, &api.UploadObjectParams{ - Branch: refName, + RefName: branchName, }, "application/octet-stream", bytes.NewReader([]byte{1, 2, 3, 4, 5, 6, 7, 8})) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) @@ -96,8 +96,8 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("success upload object", func() { resp, err := client.UploadObjectWithBody(ctx, userName, repoName, &api.UploadObjectParams{ - Branch: refName, - Path: "a.bin", + RefName: branchName, + Path: "a.bin", }, "application/octet-stream", bytes.NewReader([]byte{1, 2, 3, 4, 5, 6, 7, 8})) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) @@ -105,8 +105,8 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("success upload object on subpath", func() { resp, err := client.UploadObjectWithBody(ctx, userName, repoName, &api.UploadObjectParams{ - Branch: refName, - Path: "a/b.bin", + RefName: branchName, + Path: "a/b.bin", }, "application/octet-stream", bytes.NewReader([]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 1, 1, 1})) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) @@ -116,7 +116,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { //commit object to branch c.Convey("commit wip", func() { resp, err := client.CommitWip(ctx, userName, repoName, &api.CommitWipParams{ - RefName: refName, + RefName: branchName, Msg: "test commit msg", }) convey.So(err, convey.ShouldBeNil) @@ -128,8 +128,8 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { re := client.RequestEditors client.RequestEditors = nil resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ - Branch: refName, - Path: "a.bin", + RefName: branchName, + Path: "a.bin", }) client.RequestEditors = re convey.So(err, convey.ShouldBeNil) @@ -138,8 +138,8 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to head object in non exit user", func() { resp, err := client.HeadObject(ctx, "mock user", repoName, &api.HeadObjectParams{ - Branch: refName, - Path: "a.bin", + RefName: branchName, + Path: "a.bin", }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -147,8 +147,8 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to head object in non exit repo", func() { resp, err := client.HeadObject(ctx, userName, "fakerepo", &api.HeadObjectParams{ - Branch: refName, - Path: "a.bin", + RefName: branchName, + Path: "a.bin", }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -156,8 +156,8 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to head object in non exit branch", func() { resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ - Branch: "mockref", - Path: "a.bin", + RefName: "mockref", + Path: "a.bin", }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -165,8 +165,8 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("forbidden head object in others", func() { resp, err := client.HeadObject(ctx, "jimmy", "happygo", &api.HeadObjectParams{ - Branch: refName, - Path: "a.bin", + RefName: branchName, + Path: "a.bin", }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) @@ -174,7 +174,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("empty path", func() { resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ - Branch: refName, + RefName: branchName, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) @@ -182,8 +182,8 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("not exit path", func() { resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ - Branch: refName, - Path: "c/d.txt", + RefName: branchName, + Path: "c/d.txt", }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) @@ -191,8 +191,8 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("success to head object", func() { resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ - Branch: refName, - Path: "a.bin", + RefName: branchName, + Path: "a.bin", }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) @@ -206,8 +206,8 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { re := client.RequestEditors client.RequestEditors = nil resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ - Branch: refName, - Path: "a.bin", + RefName: branchName, + Path: "a.bin", }) client.RequestEditors = re convey.So(err, convey.ShouldBeNil) @@ -216,8 +216,8 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to get object in non exit user", func() { resp, err := client.GetObject(ctx, "mock user", repoName, &api.GetObjectParams{ - Branch: refName, - Path: "a.bin", + RefName: branchName, + Path: "a.bin", }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -225,8 +225,8 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to get object in non exit repo", func() { resp, err := client.GetObject(ctx, userName, "fakerepo", &api.GetObjectParams{ - Branch: refName, - Path: "a.bin", + RefName: branchName, + Path: "a.bin", }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -234,8 +234,8 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to get object in non exit branch", func() { resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ - Branch: "mockref", - Path: "a.bin", + RefName: "mockref", + Path: "a.bin", }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -243,8 +243,8 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("forbidden get object in others", func() { resp, err := client.GetObject(ctx, "jimmy", "happygo", &api.GetObjectParams{ - Branch: refName, - Path: "a.bin", + RefName: branchName, + Path: "a.bin", }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) @@ -252,7 +252,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("empty path", func() { resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ - Branch: refName, + RefName: branchName, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) @@ -260,8 +260,8 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("not exit path", func() { resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ - Branch: refName, - Path: "c/d.txt", + RefName: branchName, + Path: "c/d.txt", }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) @@ -269,8 +269,8 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("success to get object", func() { resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ - Branch: refName, - Path: "a.bin", + RefName: branchName, + Path: "a.bin", }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) diff --git a/integrationtest/wip_object_test.go b/integrationtest/wip_object_test.go index cbbb12ba..c182362b 100644 --- a/integrationtest/wip_object_test.go +++ b/integrationtest/wip_object_test.go @@ -16,24 +16,24 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { return func(c convey.C) { userName := "jude" repoName := "hash" - refName := "feat/wip_obj_test" + branchName := "feat/wip_obj_test" createUser(ctx, c, client, userName) loginAndSwitch(ctx, c, client, userName) createRepo(ctx, c, client, repoName) - createBranch(ctx, c, client, userName, repoName, "main", refName) - createWip(ctx, c, client, "get wip obj test", userName, repoName, refName) - uploadObject(ctx, c, client, "update f1 to test branch", userName, repoName, refName, "m.dat") - uploadObject(ctx, c, client, "update f2 to test branch", userName, repoName, refName, "g/m.dat") + createBranch(ctx, c, client, userName, repoName, "main", branchName) + createWip(ctx, c, client, "get wip obj test", userName, repoName, branchName) + uploadObject(ctx, c, client, "update f1 to test branch", userName, repoName, branchName, "m.dat") + uploadObject(ctx, c, client, "update f2 to test branch", userName, repoName, branchName, "g/m.dat") c.Convey("head object", func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors client.RequestEditors = nil resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ - Branch: refName, - Path: "m.dat", - IsWip: utils.Bool(true), + RefName: branchName, + Path: "m.dat", + IsWip: utils.Bool(true), }) client.RequestEditors = re convey.So(err, convey.ShouldBeNil) @@ -42,9 +42,9 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to head object in non exit user", func() { resp, err := client.HeadObject(ctx, "mock user", repoName, &api.HeadObjectParams{ - Branch: refName, - Path: "m.dat", - IsWip: utils.Bool(true), + RefName: branchName, + Path: "m.dat", + IsWip: utils.Bool(true), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -52,9 +52,9 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to head object in non exit repo", func() { resp, err := client.HeadObject(ctx, userName, "fakerepo", &api.HeadObjectParams{ - Branch: refName, - Path: "m.dat", - IsWip: utils.Bool(true), + RefName: branchName, + Path: "m.dat", + IsWip: utils.Bool(true), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -62,9 +62,9 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to head object in non exit branch", func() { resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ - Branch: "mockref", - Path: "m.dat", - IsWip: utils.Bool(true), + RefName: "mockref", + Path: "m.dat", + IsWip: utils.Bool(true), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -72,9 +72,9 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("forbidden head object in others", func() { resp, err := client.HeadObject(ctx, "jimmy", "happygo", &api.HeadObjectParams{ - Branch: refName, - Path: "m.dat", - IsWip: utils.Bool(true), + RefName: branchName, + Path: "m.dat", + IsWip: utils.Bool(true), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) @@ -91,9 +91,9 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("not exit path", func() { resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ - Branch: refName, - Path: "c/d.txt", - IsWip: utils.Bool(true), + RefName: branchName, + Path: "c/d.txt", + IsWip: utils.Bool(true), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) @@ -101,9 +101,9 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("success to head object", func() { resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ - Branch: refName, - Path: "m.dat", - IsWip: utils.Bool(true), + RefName: branchName, + Path: "m.dat", + IsWip: utils.Bool(true), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) @@ -115,9 +115,9 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { re := client.RequestEditors client.RequestEditors = nil resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ - Branch: refName, - Path: "m.dat", - IsWip: utils.Bool(true), + RefName: branchName, + Path: "m.dat", + IsWip: utils.Bool(true), }) client.RequestEditors = re convey.So(err, convey.ShouldBeNil) @@ -126,9 +126,9 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to get object in non exit user", func() { resp, err := client.GetObject(ctx, "mock user", repoName, &api.GetObjectParams{ - Branch: refName, - Path: "m.dat", - IsWip: utils.Bool(true), + RefName: branchName, + Path: "m.dat", + IsWip: utils.Bool(true), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -136,9 +136,9 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to get object in non exit repo", func() { resp, err := client.GetObject(ctx, userName, "fakerepo", &api.GetObjectParams{ - Branch: refName, - Path: "m.dat", - IsWip: utils.Bool(true), + RefName: branchName, + Path: "m.dat", + IsWip: utils.Bool(true), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -146,9 +146,9 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to get object in non exit branch", func() { resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ - Branch: "mockref", - Path: "m.dat", - IsWip: utils.Bool(true), + RefName: "mockref", + Path: "m.dat", + IsWip: utils.Bool(true), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -156,9 +156,9 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("forbidden get object in others", func() { resp, err := client.GetObject(ctx, "jimmy", "happygo", &api.GetObjectParams{ - Branch: refName, - Path: "m.dat", - IsWip: utils.Bool(true), + RefName: branchName, + Path: "m.dat", + IsWip: utils.Bool(true), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) @@ -166,8 +166,8 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("empty path", func() { resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ - Branch: refName, - IsWip: utils.Bool(true), + RefName: branchName, + IsWip: utils.Bool(true), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) @@ -175,9 +175,9 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("not exit path", func() { resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ - Branch: refName, - Path: "c/d.txt", - IsWip: utils.Bool(true), + RefName: branchName, + Path: "c/d.txt", + IsWip: utils.Bool(true), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) @@ -185,9 +185,9 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("success to get object", func() { resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ - Branch: refName, - Path: "m.dat", - IsWip: utils.Bool(true), + RefName: branchName, + Path: "m.dat", + IsWip: utils.Bool(true), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) @@ -199,8 +199,8 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { re := client.RequestEditors client.RequestEditors = nil resp, err := client.DeleteObject(ctx, userName, repoName, &api.DeleteObjectParams{ - Branch: refName, - Path: "g/m.dat", + RefName: branchName, + Path: "g/m.dat", }) client.RequestEditors = re convey.So(err, convey.ShouldBeNil) @@ -209,8 +209,8 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to delete object in non exit user", func() { resp, err := client.DeleteObject(ctx, "mockUser", repoName, &api.DeleteObjectParams{ - Branch: refName, - Path: "g/m.dat", + RefName: branchName, + Path: "g/m.dat", }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -218,8 +218,8 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to delete object in non exit repo", func() { resp, err := client.DeleteObject(ctx, userName, "fakerepo", &api.DeleteObjectParams{ - Branch: refName, - Path: "g/m.dat", + RefName: branchName, + Path: "g/m.dat", }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -227,8 +227,8 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to delete object in non exit branch", func() { resp, err := client.DeleteObject(ctx, userName, repoName, &api.DeleteObjectParams{ - Branch: "mockref", - Path: "g/m.dat", + RefName: "mockref", + Path: "g/m.dat", }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -236,8 +236,8 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("forbidden delete object in others", func() { resp, err := client.DeleteObject(ctx, "jimmy", "happygo", &api.DeleteObjectParams{ - Branch: "main", - Path: "g/m.dat", + RefName: "main", + Path: "g/m.dat", }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) @@ -245,8 +245,8 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("empty path", func() { resp, err := client.DeleteObject(ctx, userName, repoName, &api.DeleteObjectParams{ - Branch: refName, - Path: "", + RefName: branchName, + Path: "", }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) @@ -254,8 +254,8 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("not exit path", func() { resp, err := client.DeleteObject(ctx, userName, repoName, &api.DeleteObjectParams{ - Branch: refName, - Path: "mm/t.dat", + RefName: branchName, + Path: "mm/t.dat", }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) @@ -264,44 +264,44 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("success to delete object", func(c convey.C) { //ensure exit resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ - Branch: refName, - Path: "g/m.dat", - IsWip: utils.Bool(true), + RefName: branchName, + Path: "g/m.dat", + IsWip: utils.Bool(true), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) resp, err = client.DeleteObject(ctx, userName, repoName, &api.DeleteObjectParams{ - Branch: refName, - Path: "g/m.dat", + RefName: branchName, + Path: "g/m.dat", }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) - commitWip(ctx, c, client, "commit delete object", userName, repoName, refName, "test") + commitWip(ctx, c, client, "commit delete object", userName, repoName, branchName, "test") //ensure not exit resp, err = client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ - Branch: refName, - Path: "g/m.dat", - IsWip: utils.Bool(true), + RefName: branchName, + Path: "g/m.dat", + IsWip: utils.Bool(true), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) }) }) - uploadObject(ctx, c, client, "update f3 to test branch", userName, repoName, refName, "a/m.dat") - uploadObject(ctx, c, client, "update f4 to test branch", userName, repoName, refName, "a/b.dat") - uploadObject(ctx, c, client, "update f5 to test branch", userName, repoName, refName, "b.dat") - uploadObject(ctx, c, client, "update f6 to test branch", userName, repoName, refName, "c.dat") + uploadObject(ctx, c, client, "update f3 to test branch", userName, repoName, branchName, "a/m.dat") + uploadObject(ctx, c, client, "update f4 to test branch", userName, repoName, branchName, "a/b.dat") + uploadObject(ctx, c, client, "update f5 to test branch", userName, repoName, branchName, "b.dat") + uploadObject(ctx, c, client, "update f6 to test branch", userName, repoName, branchName, "c.dat") c.Convey("get wip changes", func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors client.RequestEditors = nil resp, err := client.GetWipChanges(ctx, userName, repoName, &api.GetWipChangesParams{ - RefName: refName, + RefName: branchName, }) client.RequestEditors = re convey.So(err, convey.ShouldBeNil) @@ -310,7 +310,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to get object in non exit user", func() { resp, err := client.GetWipChanges(ctx, "mock user", repoName, &api.GetWipChangesParams{ - RefName: refName, + RefName: branchName, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -318,7 +318,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to get object in non exit repo", func() { resp, err := client.GetWipChanges(ctx, userName, "fakerepo", &api.GetWipChangesParams{ - RefName: refName, + RefName: branchName, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -342,7 +342,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("not exit path", func() { resp, err := client.GetWipChanges(ctx, userName, repoName, &api.GetWipChangesParams{ - RefName: refName, + RefName: branchName, Path: utils.String("a/b/c/d"), }) convey.So(err, convey.ShouldBeNil) @@ -355,7 +355,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("success to get object", func() { resp, err := client.GetWipChanges(ctx, userName, repoName, &api.GetWipChangesParams{ - RefName: refName, + RefName: branchName, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) diff --git a/integrationtest/wip_test.go b/integrationtest/wip_test.go index 5c8d8b65..5f0486cd 100644 --- a/integrationtest/wip_test.go +++ b/integrationtest/wip_test.go @@ -14,14 +14,14 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { return func(c convey.C) { userName := "july" repoName := "mlops" - refName := "feat/wip_test" - refNameForDelete := "feat/wip_test2" + branchName := "feat/wip_test" + branchNameForDelete := "feat/wip_test2" createUser(ctx, c, client, userName) loginAndSwitch(ctx, c, client, userName) createRepo(ctx, c, client, repoName) - createBranch(ctx, c, client, userName, repoName, "main", refName) - createBranch(ctx, c, client, userName, repoName, "main", refNameForDelete) + createBranch(ctx, c, client, userName, repoName, "main", branchName) + createBranch(ctx, c, client, userName, repoName, "main", branchNameForDelete) c.Convey("list non exit wip", func(c convey.C) { resp, err := client.ListWip(ctx, userName, repoName) convey.So(err, convey.ShouldBeNil) @@ -37,7 +37,7 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { re := client.RequestEditors client.RequestEditors = nil resp, err := client.CreateWip(ctx, userName, repoName, &api.CreateWipParams{ - RefName: refName, + RefName: branchName, }) client.RequestEditors = re convey.So(err, convey.ShouldBeNil) @@ -46,7 +46,7 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to create branch in non exit repo", func() { resp, err := client.CreateWip(ctx, userName, "fakerepo", &api.CreateWipParams{ - RefName: refName, + RefName: branchName, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -54,7 +54,7 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to create branch in non exit user", func() { resp, err := client.CreateWip(ctx, "mock_user", "main", &api.CreateWipParams{ - RefName: refName, + RefName: branchName, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -78,7 +78,7 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("success create branch", func() { resp, err := client.CreateWip(ctx, userName, repoName, &api.CreateWipParams{ - RefName: refName, + RefName: branchName, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) @@ -86,7 +86,7 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("user only have one wip for one userName", func() { resp, err := client.CreateWip(ctx, userName, repoName, &api.CreateWipParams{ - RefName: refName, + RefName: branchName, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) @@ -139,7 +139,7 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { re := client.RequestEditors client.RequestEditors = nil resp, err := client.GetWip(ctx, userName, repoName, &api.GetWipParams{ - RefName: refName, + RefName: branchName, }) client.RequestEditors = re convey.So(err, convey.ShouldBeNil) @@ -148,7 +148,7 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("success get branch", func() { resp, err := client.GetWip(ctx, userName, repoName, &api.GetWipParams{ - RefName: refName, + RefName: branchName, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) @@ -167,7 +167,7 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to get wip from non exit user", func() { resp, err := client.GetWip(ctx, "mock_owner", repoName, &api.GetWipParams{ - RefName: refName, + RefName: branchName, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -175,7 +175,7 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to get non exit branch", func() { resp, err := client.GetWip(ctx, userName, "mock_repo", &api.GetWipParams{ - RefName: refName, + RefName: branchName, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -194,26 +194,26 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors client.RequestEditors = nil - resp, err := client.DeleteWip(ctx, userName, repoName, &api.DeleteWipParams{RefName: refNameForDelete}) + resp, err := client.DeleteWip(ctx, userName, repoName, &api.DeleteWipParams{RefName: branchNameForDelete}) client.RequestEditors = re convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("delete non exit wip", func() { - resp, err := client.DeleteWip(ctx, userName, repoName, &api.DeleteWipParams{RefName: refNameForDelete}) + resp, err := client.DeleteWip(ctx, userName, repoName, &api.DeleteWipParams{RefName: branchNameForDelete}) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) }) c.Convey("delete wip in not exit repo", func() { - resp, err := client.DeleteWip(ctx, userName, "mock_repo", &api.DeleteWipParams{RefName: refNameForDelete}) + resp, err := client.DeleteWip(ctx, userName, "mock_repo", &api.DeleteWipParams{RefName: branchNameForDelete}) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) }) c.Convey("delete wip in non exit user", func() { - resp, err := client.DeleteWip(ctx, "telo", repoName, &api.DeleteWipParams{RefName: refNameForDelete}) + resp, err := client.DeleteWip(ctx, "telo", repoName, &api.DeleteWipParams{RefName: branchNameForDelete}) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) }) @@ -224,15 +224,15 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) }) - createWip(ctx, c, client, "creat wip for test delete", userName, repoName, refNameForDelete) + createWip(ctx, c, client, "creat wip for test delete", userName, repoName, branchNameForDelete) c.Convey("delete branch successful", func() { //delete - resp, err := client.DeleteWip(ctx, userName, repoName, &api.DeleteWipParams{RefName: refNameForDelete}) + resp, err := client.DeleteWip(ctx, userName, repoName, &api.DeleteWipParams{RefName: branchNameForDelete}) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) //ensure delete work - getResp, err := client.GetWip(ctx, userName, repoName, &api.GetWipParams{RefName: refNameForDelete}) + getResp, err := client.GetWip(ctx, userName, repoName, &api.GetWipParams{RefName: branchNameForDelete}) convey.So(err, convey.ShouldBeNil) convey.So(getResp.StatusCode, convey.ShouldEqual, http.StatusNotFound) }) diff --git a/models/ref.go b/models/branch.go similarity index 51% rename from models/ref.go rename to models/branch.go index 42ee1202..4735fa47 100644 --- a/models/ref.go +++ b/models/branch.go @@ -10,8 +10,8 @@ import ( "github.com/uptrace/bun" ) -type Ref struct { - bun.BaseModel `bun:"table:refs"` +type Branches struct { + bun.BaseModel `bun:"table:branches"` ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` // RepositoryId which repository this branch belong RepositoryID uuid.UUID `bun:"repository_id,type:uuid,notnull"` @@ -27,112 +27,112 @@ type Ref struct { UpdatedAt time.Time `bun:"updated_at"` } -type GetRefParams struct { +type GetBranchParams struct { ID uuid.UUID RepositoryID uuid.UUID Name *string } -func NewGetRefParams() *GetRefParams { - return &GetRefParams{} +func NewGetBranchParams() *GetBranchParams { + return &GetBranchParams{} } -func (gup *GetRefParams) SetID(id uuid.UUID) *GetRefParams { +func (gup *GetBranchParams) SetID(id uuid.UUID) *GetBranchParams { gup.ID = id return gup } -func (gup *GetRefParams) SetRepositoryID(repositoryID uuid.UUID) *GetRefParams { +func (gup *GetBranchParams) SetRepositoryID(repositoryID uuid.UUID) *GetBranchParams { gup.RepositoryID = repositoryID return gup } -func (gup *GetRefParams) SetName(name string) *GetRefParams { +func (gup *GetBranchParams) SetName(name string) *GetBranchParams { gup.Name = &name return gup } -type DeleteRefParams struct { +type DeleteBranchParams struct { ID uuid.UUID RepositoryID uuid.UUID Name *string } -func NewDeleteRefParams() *DeleteRefParams { - return &DeleteRefParams{} +func NewDeleteBranchParams() *DeleteBranchParams { + return &DeleteBranchParams{} } -func (gup *DeleteRefParams) SetRepositoryID(repositoryID uuid.UUID) *DeleteRefParams { +func (gup *DeleteBranchParams) SetRepositoryID(repositoryID uuid.UUID) *DeleteBranchParams { gup.RepositoryID = repositoryID return gup } -func (gup *DeleteRefParams) SetID(id uuid.UUID) *DeleteRefParams { +func (gup *DeleteBranchParams) SetID(id uuid.UUID) *DeleteBranchParams { gup.ID = id return gup } -func (gup *DeleteRefParams) SetName(name string) *DeleteRefParams { +func (gup *DeleteBranchParams) SetName(name string) *DeleteBranchParams { gup.Name = &name return gup } -type UpdateRefParams struct { - bun.BaseModel `bun:"table:refs"` +type UpdateBranchParams struct { + bun.BaseModel `bun:"table:branches"` ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` CommitHash hash.Hash `bun:"commit_hash,type:bytea,notnull"` } -func NewUpdateRefParams(id uuid.UUID) *UpdateRefParams { - return &UpdateRefParams{ID: id} +func NewUpdateBranchParams(id uuid.UUID) *UpdateBranchParams { + return &UpdateBranchParams{ID: id} } -func (up *UpdateRefParams) SetCommitHash(commitHash hash.Hash) *UpdateRefParams { +func (up *UpdateBranchParams) SetCommitHash(commitHash hash.Hash) *UpdateBranchParams { up.CommitHash = commitHash return up } -type ListRefParams struct { +type ListBranchParams struct { RepositoryID uuid.UUID } -func NewListRefParams() *ListRefParams { - return &ListRefParams{} +func NewListBranchParams() *ListBranchParams { + return &ListBranchParams{} } -func (gup *ListRefParams) SetRepositoryID(repositoryID uuid.UUID) *ListRefParams { +func (gup *ListBranchParams) SetRepositoryID(repositoryID uuid.UUID) *ListBranchParams { gup.RepositoryID = repositoryID return gup } -type IRefRepo interface { - Insert(ctx context.Context, repo *Ref) (*Ref, error) - UpdateByID(ctx context.Context, params *UpdateRefParams) error - Get(ctx context.Context, id *GetRefParams) (*Ref, error) +type IBranchRepo interface { + Insert(ctx context.Context, repo *Branches) (*Branches, error) + UpdateByID(ctx context.Context, params *UpdateBranchParams) error + Get(ctx context.Context, id *GetBranchParams) (*Branches, error) - List(ctx context.Context, params *ListRefParams) ([]*Ref, error) - Delete(ctx context.Context, params *DeleteRefParams) (int64, error) + List(ctx context.Context, params *ListBranchParams) ([]*Branches, error) + Delete(ctx context.Context, params *DeleteBranchParams) (int64, error) } -var _ IRefRepo = (*RefRepo)(nil) +var _ IBranchRepo = (*BranchRepo)(nil) -type RefRepo struct { +type BranchRepo struct { db bun.IDB } -func NewRefRepo(db bun.IDB) IRefRepo { - return &RefRepo{db: db} +func NewBranchRepo(db bun.IDB) IBranchRepo { + return &BranchRepo{db: db} } -func (r RefRepo) Insert(ctx context.Context, ref *Ref) (*Ref, error) { - _, err := r.db.NewInsert().Model(ref).Exec(ctx) +func (r BranchRepo) Insert(ctx context.Context, branch *Branches) (*Branches, error) { + _, err := r.db.NewInsert().Model(branch).Exec(ctx) if err != nil { return nil, err } - return ref, nil + return branch, nil } -func (r RefRepo) Get(ctx context.Context, params *GetRefParams) (*Ref, error) { - repo := &Ref{} +func (r BranchRepo) Get(ctx context.Context, params *GetBranchParams) (*Branches, error) { + repo := &Branches{} query := r.db.NewSelect().Model(repo) if uuid.Nil != params.ID { @@ -154,9 +154,9 @@ func (r RefRepo) Get(ctx context.Context, params *GetRefParams) (*Ref, error) { return repo, nil } -func (r RefRepo) List(ctx context.Context, params *ListRefParams) ([]*Ref, error) { - var refs []*Ref - query := r.db.NewSelect().Model(&refs) +func (r BranchRepo) List(ctx context.Context, params *ListBranchParams) ([]*Branches, error) { + var branches []*Branches + query := r.db.NewSelect().Model(&branches) if uuid.Nil != params.RepositoryID { query = query.Where("repository_id = ?", params.RepositoryID) @@ -166,11 +166,11 @@ func (r RefRepo) List(ctx context.Context, params *ListRefParams) ([]*Ref, error if err != nil { return nil, err } - return refs, nil + return branches, nil } -func (r RefRepo) Delete(ctx context.Context, params *DeleteRefParams) (int64, error) { - query := r.db.NewDelete().Model((*Ref)(nil)) +func (r BranchRepo) Delete(ctx context.Context, params *DeleteBranchParams) (int64, error) { + query := r.db.NewDelete().Model((*Branches)(nil)) if uuid.Nil != params.ID { query = query.Where("id = ?", params.ID) @@ -195,7 +195,7 @@ func (r RefRepo) Delete(ctx context.Context, params *DeleteRefParams) (int64, er return affectedRows, err } -func (r RefRepo) UpdateByID(ctx context.Context, updateModel *UpdateRefParams) error { +func (r BranchRepo) UpdateByID(ctx context.Context, updateModel *UpdateBranchParams) error { _, err := r.db.NewUpdate().Model(updateModel).WherePK().Exec(ctx) return err } diff --git a/models/branch_test.go b/models/branch_test.go new file mode 100644 index 00000000..244219f0 --- /dev/null +++ b/models/branch_test.go @@ -0,0 +1,60 @@ +package models_test + +import ( + "context" + "testing" + + "github.com/jiaozifs/jiaozifs/utils/hash" + + "github.com/brianvoe/gofakeit/v6" + "github.com/google/go-cmp/cmp" + "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/stretchr/testify/require" +) + +func TestRefRepoInsert(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + repo := models.NewBranchRepo(db) + + branchModel := &models.Branches{} + require.NoError(t, gofakeit.Struct(branchModel)) + newBrance, err := repo.Insert(ctx, branchModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newBrance.ID) + + getBranchParams := models.NewGetBranchParams(). + SetID(newBrance.ID). + SetRepositoryID(newBrance.RepositoryID). + SetName(newBrance.Name) + branch, err := repo.Get(ctx, getBranchParams) + require.NoError(t, err) + + require.True(t, cmp.Equal(branchModel, branch, dbTimeCmpOpt)) + + mockHash := hash.Hash("mock hash") + err = repo.UpdateByID(ctx, models.NewUpdateBranchParams(newBrance.ID).SetCommitHash(mockHash)) + require.NoError(t, err) + + branchAfterUpdated, err := repo.Get(ctx, &models.GetBranchParams{ + ID: newBrance.ID, + }) + require.NoError(t, err) + require.Equal(t, mockHash, branchAfterUpdated.CommitHash) + + list, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID)) + require.NoError(t, err) + require.Len(t, list, 1) + + affectedRows, err := repo.Delete(ctx, models.NewDeleteBranchParams().SetID(list[0].ID).SetRepositoryID(list[0].RepositoryID).SetName(list[0].Name)) + require.NoError(t, err) + require.Equal(t, int64(1), affectedRows) + + list, err = repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID)) + require.NoError(t, err) + require.Len(t, list, 0) +} diff --git a/models/migrations/20210505110026_init_project.go b/models/migrations/20210505110026_init_project.go index 3cd8c957..55de291a 100644 --- a/models/migrations/20210505110026_init_project.go +++ b/models/migrations/20210505110026_init_project.go @@ -41,7 +41,7 @@ func init() { //ref _, err = db.NewCreateTable(). - Model((*models.Ref)(nil)). + Model((*models.Branches)(nil)). Exec(ctx) if err != nil { return err diff --git a/models/ref_test.go b/models/ref_test.go deleted file mode 100644 index 8f45c792..00000000 --- a/models/ref_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package models_test - -import ( - "context" - "testing" - - "github.com/jiaozifs/jiaozifs/utils/hash" - - "github.com/brianvoe/gofakeit/v6" - "github.com/google/go-cmp/cmp" - "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/testhelper" - "github.com/stretchr/testify/require" -) - -func TestRefRepoInsert(t *testing.T) { - ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint - - repo := models.NewRefRepo(db) - - refModel := &models.Ref{} - require.NoError(t, gofakeit.Struct(refModel)) - newRef, err := repo.Insert(ctx, refModel) - require.NoError(t, err) - require.NotEqual(t, uuid.Nil, newRef.ID) - - getRefParams := models.NewGetRefParams(). - SetID(newRef.ID). - SetRepositoryID(newRef.RepositoryID). - SetName(newRef.Name) - ref, err := repo.Get(ctx, getRefParams) - require.NoError(t, err) - - require.True(t, cmp.Equal(refModel, ref, dbTimeCmpOpt)) - - mockHash := hash.Hash("mock hash") - err = repo.UpdateByID(ctx, models.NewUpdateRefParams(newRef.ID).SetCommitHash(mockHash)) - require.NoError(t, err) - - refAfterUpdated, err := repo.Get(ctx, &models.GetRefParams{ - ID: newRef.ID, - }) - require.NoError(t, err) - require.Equal(t, mockHash, refAfterUpdated.CommitHash) - - list, err := repo.List(ctx, models.NewListRefParams().SetRepositoryID(ref.RepositoryID)) - require.NoError(t, err) - require.Len(t, list, 1) - - affectedRows, err := repo.Delete(ctx, models.NewDeleteRefParams().SetID(list[0].ID).SetRepositoryID(list[0].RepositoryID).SetName(list[0].Name)) - require.NoError(t, err) - require.Equal(t, int64(1), affectedRows) - - list, err = repo.List(ctx, models.NewListRefParams().SetRepositoryID(ref.RepositoryID)) - require.NoError(t, err) - require.Len(t, list, 0) -} diff --git a/models/repo.go b/models/repo.go index 1d7cdaef..50ff97e7 100644 --- a/models/repo.go +++ b/models/repo.go @@ -32,7 +32,7 @@ type IRepo interface { FileTreeRepo(repoID uuid.UUID) IFileTreeRepo CommitRepo(repoID uuid.UUID) ICommitRepo TagRepo(repoID uuid.UUID) ITagRepo - RefRepo() IRefRepo + BranchRepo() IBranchRepo RepositoryRepo() IRepositoryRepo WipRepo() IWipRepo } @@ -73,8 +73,8 @@ func (repo *PgRepo) TagRepo(repoID uuid.UUID) ITagRepo { return NewTagRepo(repo.db, repoID) } -func (repo *PgRepo) RefRepo() IRefRepo { - return NewRefRepo(repo.db) +func (repo *PgRepo) BranchRepo() IBranchRepo { + return NewBranchRepo(repo.db) } func (repo *PgRepo) RepositoryRepo() IRepositoryRepo { diff --git a/versionmgr/commit_test.go b/versionmgr/commit_test.go index 3f34e7f6..2328ba9e 100644 --- a/versionmgr/commit_test.go +++ b/versionmgr/commit_test.go @@ -37,7 +37,7 @@ func TestCommitOpDiffCommit(t *testing.T) { require.NoError(t, err) //base branch - baseRef, err := makeRef(ctx, repo.RefRepo(), "feat/base", project.ID, hash.Hash("a")) + baseBranch, err := makeBranch(ctx, repo.BranchRepo(), "feat/base", project.ID, hash.Hash("a")) require.NoError(t, err) //commit1 a.txt b/c.txt b/e.txt @@ -50,7 +50,7 @@ func TestCommitOpDiffCommit(t *testing.T) { root1, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), EmptyDirEntry, testData1) require.NoError(t, err) - baseWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseRef.ID, EmptyRoot.Hash, root1.Hash) + baseWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseBranch.ID, EmptyRoot.Hash, root1.Hash) require.NoError(t, err) baseCommit, err := NewCommitOp(repo, project.ID, nil).AddCommit(ctx, user, baseWip.ID, "base commit") @@ -66,7 +66,7 @@ func TestCommitOpDiffCommit(t *testing.T) { root2, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(root1.Hash), testData2) require.NoError(t, err) - secondWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseRef.ID, EmptyRoot.Hash, root2.Hash) + secondWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseBranch.ID, EmptyRoot.Hash, root2.Hash) require.NoError(t, err) secondCommit, err := NewCommitOp(repo, project.ID, nil).AddCommit(ctx, user, secondWip.ID, "merge commit") require.NoError(t, err) @@ -127,10 +127,10 @@ func TestCommitOpMerge(t *testing.T) { oriRoot, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), EmptyDirEntry, testData) require.NoError(t, err) //base branch - baseRef, err := makeRef(ctx, repo.RefRepo(), "feat/base", project.ID, hash.Hash("a")) + baseBranch, err := makeBranch(ctx, repo.BranchRepo(), "feat/base", project.ID, hash.Hash("a")) require.NoError(t, err) - oriWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseRef.ID, hash.Hash{}, oriRoot.Hash) + oriWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseBranch.ID, hash.Hash{}, oriRoot.Hash) require.NoError(t, err) oriCommit, err := NewCommitOp(repo, project.ID, nil).AddCommit(ctx, user, oriWip.ID, "") @@ -145,7 +145,7 @@ func TestCommitOpMerge(t *testing.T) { baseModify, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(oriRoot.Hash), testData) require.NoError(t, err) - baseWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseRef.ID, oriRoot.Hash, baseModify.Hash) + baseWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseBranch.ID, oriRoot.Hash, baseModify.Hash) require.NoError(t, err) commitA, err := NewCommitOp(repo, project.ID, oriCommit.Commit()).AddCommit(ctx, user, baseWip.ID, "commit a") @@ -153,7 +153,7 @@ func TestCommitOpMerge(t *testing.T) { require.NoError(t, rmWip(ctx, repo.WipRepo(), baseWip.ID)) //toMerge branch - mergeRef, err := makeRef(ctx, repo.RefRepo(), "feat/merge", project.ID, hash.Hash("a")) + mergeBranch, err := makeBranch(ctx, repo.BranchRepo(), "feat/merge", project.ID, hash.Hash("a")) require.NoError(t, err) //modify a.txt @@ -164,7 +164,7 @@ func TestCommitOpMerge(t *testing.T) { ` mergeModify, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(oriRoot.Hash), testData) require.NoError(t, err) - mergeWip, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeRef.ID, oriRoot.Hash, mergeModify.Hash) + mergeWip, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeBranch.ID, oriRoot.Hash, mergeModify.Hash) require.NoError(t, err) commitB, err := NewCommitOp(repo, project.ID, oriCommit.Commit()).AddCommit(ctx, user, mergeWip.ID, "commit b") require.NoError(t, err) @@ -180,7 +180,7 @@ func TestCommitOpMerge(t *testing.T) { ` rootF, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(commitAB.TreeHash), testData) require.NoError(t, err) - mergeWipF, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeRef.ID, commitAB.TreeHash, rootF.Hash) + mergeWipF, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeBranch.ID, commitAB.TreeHash, rootF.Hash) require.NoError(t, err) commitF, err := NewCommitOp(repo, project.ID, oriCommit.Commit()).AddCommit(ctx, user, mergeWipF.ID, "commit f") require.NoError(t, err) @@ -198,7 +198,7 @@ func TestCommitOpMerge(t *testing.T) { ` modifyD, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(commitB.Commit().TreeHash), testData) require.NoError(t, err) - mergeWipD, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeRef.ID, commitB.Commit().Hash, modifyD.Hash) + mergeWipD, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeBranch.ID, commitB.Commit().Hash, modifyD.Hash) require.NoError(t, err) commitD, err := commitB.AddCommit(ctx, user, mergeWipD.ID, "commit d") require.NoError(t, err) @@ -209,7 +209,7 @@ func TestCommitOpMerge(t *testing.T) { ` modifyE, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(commitD.Commit().TreeHash), testData) require.NoError(t, err) - mergeWipE, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeRef.ID, commitD.Commit().Hash, modifyE.Hash) + mergeWipE, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeBranch.ID, commitD.Commit().Hash, modifyE.Hash) require.NoError(t, err) commitE, err := commitD.AddCommit(ctx, user, mergeWipE.ID, "commit e") require.NoError(t, err) @@ -259,10 +259,10 @@ func TestCrissCrossMerge(t *testing.T) { oriRoot, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), EmptyDirEntry, testData) require.NoError(t, err) //base branch - baseRef, err := makeRef(ctx, repo.RefRepo(), "feat/base", project.ID, hash.Hash("a")) + baseBranch, err := makeBranch(ctx, repo.BranchRepo(), "feat/base", project.ID, hash.Hash("a")) require.NoError(t, err) - oriWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseRef.ID, hash.Hash{}, oriRoot.Hash) + oriWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseBranch.ID, hash.Hash{}, oriRoot.Hash) require.NoError(t, err) oriCommit, err := NewCommitOp(repo, project.ID, nil).AddCommit(ctx, user, oriWip.ID, "") @@ -277,7 +277,7 @@ func TestCrissCrossMerge(t *testing.T) { baseModify, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(oriRoot.Hash), testData) require.NoError(t, err) - baseWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseRef.ID, oriRoot.Hash, baseModify.Hash) + baseWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseBranch.ID, oriRoot.Hash, baseModify.Hash) require.NoError(t, err) commitA, err := NewCommitOp(repo, project.ID, oriCommit.Commit()).AddCommit(ctx, user, baseWip.ID, "commit a") @@ -285,7 +285,7 @@ func TestCrissCrossMerge(t *testing.T) { require.NoError(t, rmWip(ctx, repo.WipRepo(), baseWip.ID)) //toMerge branch - mergeRef, err := makeRef(ctx, repo.RefRepo(), "feat/merge", project.ID, hash.Hash("a")) + mergeBranch, err := makeBranch(ctx, repo.BranchRepo(), "feat/merge", project.ID, hash.Hash("a")) require.NoError(t, err) //modify a.txt @@ -296,7 +296,7 @@ func TestCrissCrossMerge(t *testing.T) { ` mergeModify, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(oriRoot.Hash), testData) require.NoError(t, err) - mergeWip, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeRef.ID, oriRoot.Hash, mergeModify.Hash) + mergeWip, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeBranch.ID, oriRoot.Hash, mergeModify.Hash) require.NoError(t, err) commitB, err := NewCommitOp(repo, project.ID, oriCommit.Commit()).AddCommit(ctx, user, mergeWip.ID, "commit b") require.NoError(t, err) @@ -364,8 +364,8 @@ func makeCommit(ctx context.Context, commitRepo models.ICommitRepo, treeHash has return obj, nil } -func makeRef(ctx context.Context, refRepo models.IRefRepo, name string, repoID uuid.UUID, commitHash hash.Hash) (*models.Ref, error) { - ref := &models.Ref{ +func makeBranch(ctx context.Context, branchRepo models.IBranchRepo, name string, repoID uuid.UUID, commitHash hash.Hash) (*models.Branches, error) { + branch := &models.Branches{ RepositoryID: repoID, CommitHash: commitHash, Name: name, @@ -374,14 +374,14 @@ func makeRef(ctx context.Context, refRepo models.IRefRepo, name string, repoID u CreatedAt: time.Time{}, UpdatedAt: time.Time{}, } - return refRepo.Insert(ctx, ref) + return branchRepo.Insert(ctx, branch) } -func makeWip(ctx context.Context, wipRepo models.IWipRepo, repoID, refID uuid.UUID, parentHash, curHash hash.Hash) (*models.WorkingInProcess, error) { +func makeWip(ctx context.Context, wipRepo models.IWipRepo, repoID, branchID uuid.UUID, parentHash, curHash hash.Hash) (*models.WorkingInProcess, error) { wip := &models.WorkingInProcess{ CurrentTree: curHash, BaseCommit: parentHash, - RefID: refID, + RefID: branchID, RepositoryID: repoID, CreatorID: uuid.UUID{}, CreatedAt: time.Time{}, From bd1b954408819f4612b94d8496465156dd404070 Mon Sep 17 00:00:00 2001 From: zjy Date: Thu, 21 Dec 2023 08:48:17 +0800 Subject: [PATCH 098/210] feat: add repo list pagination --- api/jiaozifs.gen.go | 335 ++++++++++++++++++++++++++--------- api/resp.gen.go | 1 + api/swagger.yml | 32 ++-- controller/repository_ctl.go | 107 ++++++++++- integrationtest/repo_test.go | 18 +- models/repository.go | 26 ++- 6 files changed, 401 insertions(+), 118 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 435d3fc9..bdc40454 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -186,6 +186,12 @@ type Repository struct { UpdatedAt time.Time `json:"UpdatedAt"` } +// RepositoryList defines model for RepositoryList. +type RepositoryList struct { + Pagination Pagination `json:"pagination"` + Results []Repository `json:"results"` +} + // SetupState defines model for SetupState. type SetupState struct { // CommPrefsMissing true if the comm prefs are missing. @@ -258,6 +264,15 @@ type Wip struct { UpdatedAt *time.Time `json:"UpdatedAt,omitempty"` } +// PaginationAfter defines model for PaginationAfter. +type PaginationAfter = string + +// PaginationAmount defines model for PaginationAmount. +type PaginationAmount = int + +// PaginationPrefix defines model for PaginationPrefix. +type PaginationPrefix = string + // LoginJSONBody defines parameters for Login. type LoginJSONBody struct { Password string `json:"password"` @@ -352,9 +367,28 @@ type GetEntriesInRefParams struct { IsWip *bool `form:"isWip,omitempty" json:"isWip,omitempty"` } +// ListRepositoryOfAuthenticatedUserParams defines parameters for ListRepositoryOfAuthenticatedUser. +type ListRepositoryOfAuthenticatedUserParams struct { + // Prefix return items prefixed with this value + Prefix *PaginationPrefix `form:"prefix,omitempty" json:"prefix,omitempty"` + + // After return items after this value + After *PaginationAfter `form:"after,omitempty" json:"after,omitempty"` + + // Amount how many items to return + Amount *PaginationAmount `form:"amount,omitempty" json:"amount,omitempty"` +} + // ListRepositoryParams defines parameters for ListRepository. type ListRepositoryParams struct { - RepoPrefix *string `form:"repoPrefix,omitempty" json:"repoPrefix,omitempty"` + // Prefix return items prefixed with this value + Prefix *PaginationPrefix `form:"prefix,omitempty" json:"prefix,omitempty"` + + // After return items after this value + After *PaginationAfter `form:"after,omitempty" json:"after,omitempty"` + + // Amount how many items to return + Amount *PaginationAmount `form:"amount,omitempty" json:"amount,omitempty"` } // DeleteWipParams defines parameters for DeleteWip. @@ -547,7 +581,7 @@ type ClientInterface interface { Register(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // ListRepositoryOfAuthenticatedUser request - ListRepositoryOfAuthenticatedUser(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + ListRepositoryOfAuthenticatedUser(ctx context.Context, params *ListRepositoryOfAuthenticatedUserParams, reqEditors ...RequestEditorFn) (*http.Response, error) // CreateRepositoryWithBody request with any body CreateRepositoryWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -846,8 +880,8 @@ func (c *Client) Register(ctx context.Context, body RegisterJSONRequestBody, req return c.Client.Do(req) } -func (c *Client) ListRepositoryOfAuthenticatedUser(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewListRepositoryOfAuthenticatedUserRequest(c.Server) +func (c *Client) ListRepositoryOfAuthenticatedUser(ctx context.Context, params *ListRepositoryOfAuthenticatedUserParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListRepositoryOfAuthenticatedUserRequest(c.Server, params) if err != nil { return nil, err } @@ -2050,7 +2084,7 @@ func NewRegisterRequestWithBody(server string, contentType string, body io.Reade } // NewListRepositoryOfAuthenticatedUserRequest generates requests for ListRepositoryOfAuthenticatedUser -func NewListRepositoryOfAuthenticatedUserRequest(server string) (*http.Request, error) { +func NewListRepositoryOfAuthenticatedUserRequest(server string, params *ListRepositoryOfAuthenticatedUserParams) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -2068,6 +2102,60 @@ func NewListRepositoryOfAuthenticatedUserRequest(server string) (*http.Request, return nil, err } + if params != nil { + queryValues := queryURL.Query() + + if params.Prefix != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "prefix", runtime.ParamLocationQuery, *params.Prefix); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.After != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "after", runtime.ParamLocationQuery, *params.After); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Amount != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "amount", runtime.ParamLocationQuery, *params.Amount); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err @@ -2172,9 +2260,41 @@ func NewListRepositoryRequest(server string, owner string, params *ListRepositor if params != nil { queryValues := queryURL.Query() - if params.RepoPrefix != nil { + if params.Prefix != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "repoPrefix", runtime.ParamLocationQuery, *params.RepoPrefix); err != nil { + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "prefix", runtime.ParamLocationQuery, *params.Prefix); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.After != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "after", runtime.ParamLocationQuery, *params.After); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Amount != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "amount", runtime.ParamLocationQuery, *params.Amount); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -2696,7 +2816,7 @@ type ClientWithResponsesInterface interface { RegisterWithResponse(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterResponse, error) // ListRepositoryOfAuthenticatedUserWithResponse request - ListRepositoryOfAuthenticatedUserWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListRepositoryOfAuthenticatedUserResponse, error) + ListRepositoryOfAuthenticatedUserWithResponse(ctx context.Context, params *ListRepositoryOfAuthenticatedUserParams, reqEditors ...RequestEditorFn) (*ListRepositoryOfAuthenticatedUserResponse, error) // CreateRepositoryWithBodyWithResponse request with any body CreateRepositoryWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateRepositoryResponse, error) @@ -3122,7 +3242,7 @@ func (r RegisterResponse) StatusCode() int { type ListRepositoryOfAuthenticatedUserResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *[]Repository + JSON200 *RepositoryList } // Status returns HTTPResponse.Status @@ -3188,7 +3308,7 @@ func (r GetUserInfoResponse) StatusCode() int { type ListRepositoryResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *[]Repository + JSON200 *RepositoryList } // Status returns HTTPResponse.Status @@ -3555,8 +3675,8 @@ func (c *ClientWithResponses) RegisterWithResponse(ctx context.Context, body Reg } // ListRepositoryOfAuthenticatedUserWithResponse request returning *ListRepositoryOfAuthenticatedUserResponse -func (c *ClientWithResponses) ListRepositoryOfAuthenticatedUserWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListRepositoryOfAuthenticatedUserResponse, error) { - rsp, err := c.ListRepositoryOfAuthenticatedUser(ctx, reqEditors...) +func (c *ClientWithResponses) ListRepositoryOfAuthenticatedUserWithResponse(ctx context.Context, params *ListRepositoryOfAuthenticatedUserParams, reqEditors ...RequestEditorFn) (*ListRepositoryOfAuthenticatedUserResponse, error) { + rsp, err := c.ListRepositoryOfAuthenticatedUser(ctx, params, reqEditors...) if err != nil { return nil, err } @@ -4064,7 +4184,7 @@ func ParseListRepositoryOfAuthenticatedUserResponse(rsp *http.Response) (*ListRe switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []Repository + var dest RepositoryList if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -4142,7 +4262,7 @@ func ParseListRepositoryResponse(rsp *http.Response) (*ListRepositoryResponse, e switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []Repository + var dest RepositoryList if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -4383,7 +4503,7 @@ type ServerInterface interface { Register(ctx context.Context, w *JiaozifsResponse, r *http.Request, body RegisterJSONRequestBody) // list repository // (GET /users/repos) - ListRepositoryOfAuthenticatedUser(ctx context.Context, w *JiaozifsResponse, r *http.Request) + ListRepositoryOfAuthenticatedUser(ctx context.Context, w *JiaozifsResponse, r *http.Request, params ListRepositoryOfAuthenticatedUserParams) // create repository // (POST /users/repos) CreateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateRepositoryJSONRequestBody) @@ -4529,7 +4649,7 @@ func (_ Unimplemented) Register(ctx context.Context, w *JiaozifsResponse, r *htt // list repository // (GET /users/repos) -func (_ Unimplemented) ListRepositoryOfAuthenticatedUser(ctx context.Context, w *JiaozifsResponse, r *http.Request) { +func (_ Unimplemented) ListRepositoryOfAuthenticatedUser(ctx context.Context, w *JiaozifsResponse, r *http.Request, params ListRepositoryOfAuthenticatedUserParams) { w.WriteHeader(http.StatusNotImplemented) } @@ -5570,14 +5690,43 @@ func (siw *ServerInterfaceWrapper) Register(w http.ResponseWriter, r *http.Reque func (siw *ServerInterfaceWrapper) ListRepositoryOfAuthenticatedUser(w http.ResponseWriter, r *http.Request) { ctx := r.Context() + var err error + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) ctx = context.WithValue(ctx, Basic_authScopes, []string{}) ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context + var params ListRepositoryOfAuthenticatedUserParams + + // ------------- Optional query parameter "prefix" ------------- + + err = runtime.BindQueryParameter("form", true, false, "prefix", r.URL.Query(), ¶ms.Prefix) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "prefix", Err: err}) + return + } + + // ------------- Optional query parameter "after" ------------- + + err = runtime.BindQueryParameter("form", true, false, "after", r.URL.Query(), ¶ms.After) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "after", Err: err}) + return + } + + // ------------- Optional query parameter "amount" ------------- + + err = runtime.BindQueryParameter("form", true, false, "amount", r.URL.Query(), ¶ms.Amount) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "amount", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.ListRepositoryOfAuthenticatedUser(r.Context(), &JiaozifsResponse{w}, r) + siw.Handler.ListRepositoryOfAuthenticatedUser(r.Context(), &JiaozifsResponse{w}, r, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -5663,11 +5812,27 @@ func (siw *ServerInterfaceWrapper) ListRepository(w http.ResponseWriter, r *http // Parameter object where we will unmarshal all parameters from the context var params ListRepositoryParams - // ------------- Optional query parameter "repoPrefix" ------------- + // ------------- Optional query parameter "prefix" ------------- + + err = runtime.BindQueryParameter("form", true, false, "prefix", r.URL.Query(), ¶ms.Prefix) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "prefix", Err: err}) + return + } + + // ------------- Optional query parameter "after" ------------- + + err = runtime.BindQueryParameter("form", true, false, "after", r.URL.Query(), ¶ms.After) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "after", Err: err}) + return + } + + // ------------- Optional query parameter "amount" ------------- - err = runtime.BindQueryParameter("form", true, false, "repoPrefix", r.URL.Query(), ¶ms.RepoPrefix) + err = runtime.BindQueryParameter("form", true, false, "amount", r.URL.Query(), ¶ms.Amount) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repoPrefix", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "amount", Err: err}) return } @@ -6268,70 +6433,72 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "HQAA//+t9VIHoF4AAA==", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/resp.gen.go b/api/resp.gen.go index b726ce38..e66c7ab1 100644 --- a/api/resp.gen.go +++ b/api/resp.gen.go @@ -5,6 +5,7 @@ // // mockgen --package=api --destination=resp.gen.go net/http ResponseWriter // + // Package api is a generated GoMock package. package api diff --git a/api/swagger.yml b/api/swagger.yml index 21503ea6..2b2931f1 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -179,6 +179,18 @@ components: properties: Description: type: string + RepositoryList: + type: object + required: + - pagination + - results + properties: + pagination: + $ref: "#/components/schemas/Pagination" + results: + type: array + items: + $ref: "#/components/schemas/Repository" Repository: type: object required: @@ -1200,20 +1212,16 @@ paths: operationId: listRepository summary: list repository in specific owner parameters: - - in: query - name: repoPrefix - required: false - schema: - type: string + - $ref: "#/components/parameters/PaginationPrefix" + - $ref: "#/components/parameters/PaginationAfter" + - $ref: "#/components/parameters/PaginationAmount" responses: 200: description: repository list content: application/json: schema: - type: array - items: - $ref: "#/components/schemas/Repository" + $ref: "#/components/schemas/RepositoryList" 400: description: ValidationError 401: @@ -1227,15 +1235,17 @@ paths: - repo operationId: listRepositoryOfAuthenticatedUser" summary: list repository + parameters: + - $ref: "#/components/parameters/PaginationPrefix" + - $ref: "#/components/parameters/PaginationAfter" + - $ref: "#/components/parameters/PaginationAmount" responses: 200: description: list repository content: application/json: schema: - type: array - items: - $ref: "#/components/schemas/Repository" + $ref: "#/components/schemas/RepositoryList" 400: description: ValidationError 401: diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index 7d3b8d30..b984e91a 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -5,6 +5,7 @@ import ( "errors" "io" "net/http" + "reflect" "regexp" "time" @@ -24,7 +25,10 @@ import ( "go.uber.org/fx" ) -const DefaultBranchName = "main" +const ( + DefaultBranchName = "main" + DefaultMaxPerPage int = 1000 +) var maxNameLength = 20 var alphanumeric = regexp.MustCompile("^[a-zA-Z0-9_]*$") @@ -32,6 +36,28 @@ var alphanumeric = regexp.MustCompile("^[a-zA-Z0-9_]*$") // RepoNameBlackList forbid repo name, reserve for routes var RepoNameBlackList = []string{"repository", "repositories", "wip", "wips", "object", "objects", "commit", "commits", "ref", "refs", "repo", "repos", "user", "users"} +func paginationForRepos(hasMore bool, results interface{}, fieldName string) api.Pagination { + pagination := api.Pagination{ + HasMore: hasMore, + MaxPerPage: DefaultMaxPerPage, + } + if results == nil { + return pagination + } + if reflect.TypeOf(results).Kind() != reflect.Slice { + panic("results is not a slice") + } + s := reflect.ValueOf(results) + pagination.Results = s.Len() + if !hasMore || pagination.Results == 0 { + return pagination + } + v := s.Index(pagination.Results - 1) + token := v.FieldByName(fieldName) + t := token.Interface().(time.Time) + pagination.NextOffset = t.String() + return pagination +} func CheckRepositoryName(name string) error { for _, blackName := range RepoNameBlackList { if name == blackName { @@ -54,19 +80,54 @@ type RepositoryController struct { Repo models.IRepo } -func (repositoryCtl RepositoryController) ListRepositoryOfAuthenticatedUser(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request) { +func (repositoryCtl RepositoryController) ListRepositoryOfAuthenticatedUser(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, params api.ListRepositoryOfAuthenticatedUserParams) { operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) return } - repositories, err := repositoryCtl.Repo.RepositoryRepo().List(ctx, models.NewListRepoParams().SetOwnerID(operator.ID)) //operator is owner + listParams := models.NewListRepoParams() + if params.Prefix != nil && len(*params.Prefix) > 0 { + listParams.SetName(*params.Prefix, models.PrefixMatch) + } + if params.After != nil { + listParams.SetAfter(*params.After) + } + if params.Amount != nil { + i := *params.Amount + if i > DefaultMaxPerPage || i <= 0 { + listParams.SetAmount(DefaultMaxPerPage) + } else { + listParams.SetAmount(i) + } + } else { + listParams.SetAmount(DefaultMaxPerPage) + } + + repositories, hasMore, err := repositoryCtl.Repo.RepositoryRepo().List(ctx, listParams. + SetOwnerID(operator.ID)) if err != nil { w.Error(err) return } - w.JSON(repositories) + results := make([]api.Repository, 0, len(repositories)) + for _, repo := range repositories { + r := api.Repository{ + CreatedAt: repo.CreatedAt, + CreatorID: repo.CreatorID, + Description: repo.Description, + Head: repo.HEAD, + ID: repo.ID, + Name: repo.Name, + UpdatedAt: repo.UpdatedAt, + } + results = append(results, r) + } + w.JSON(api.RepositoryList{ + Pagination: paginationForRepos(hasMore, results, "UpdatedAt"), + Results: results, + }) } func (repositoryCtl RepositoryController) ListRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, params api.ListRepositoryParams) { @@ -87,15 +148,45 @@ func (repositoryCtl RepositoryController) ListRepository(ctx context.Context, w } listParams := models.NewListRepoParams().SetOwnerID(owner.ID) - if params.RepoPrefix != nil && len(*params.RepoPrefix) > 0 { - listParams.SetName(*params.RepoPrefix, models.PrefixMatch) + if params.Prefix != nil && len(*params.Prefix) > 0 { + listParams.SetName(*params.Prefix, models.PrefixMatch) + } + if params.After != nil { + listParams.SetAfter(*params.After) + } + if params.Amount != nil { + i := *params.Amount + if i > DefaultMaxPerPage || i <= 0 { + listParams.SetAmount(DefaultMaxPerPage) + } else { + listParams.SetAmount(i) + } + } else { + listParams.SetAmount(DefaultMaxPerPage) } - repositories, err := repositoryCtl.Repo.RepositoryRepo().List(ctx, listParams) + + repositories, hasMore, err := repositoryCtl.Repo.RepositoryRepo().List(ctx, listParams) if err != nil { w.Error(err) return } - w.JSON(repositories) + results := make([]api.Repository, 0, len(repositories)) + for _, repo := range repositories { + r := api.Repository{ + CreatedAt: repo.CreatedAt, + CreatorID: repo.CreatorID, + Description: repo.Description, + Head: repo.HEAD, + ID: repo.ID, + Name: repo.Name, + UpdatedAt: repo.UpdatedAt, + } + results = append(results, r) + } + w.JSON(api.RepositoryList{ + Pagination: paginationForRepos(hasMore, results, "UpdatedAt"), + Results: results, + }) } func (repositoryCtl RepositoryController) CreateRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.CreateRepositoryJSONRequestBody) { diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index 2ae37f3e..23361960 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -99,21 +99,21 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors client.RequestEditors = nil - resp, err := client.ListRepositoryOfAuthenticatedUser(ctx) + resp, err := client.ListRepositoryOfAuthenticatedUser(ctx, &api.ListRepositoryOfAuthenticatedUserParams{}) client.RequestEditors = re convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("list repository in authenticated user", func() { - resp, err := client.ListRepositoryOfAuthenticatedUser(ctx) + resp, err := client.ListRepositoryOfAuthenticatedUser(ctx, &api.ListRepositoryOfAuthenticatedUserParams{}) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) listRepos, err := api.ParseListRepositoryResponse(resp) convey.So(err, convey.ShouldBeNil) - convey.So(len(*listRepos.JSON200), convey.ShouldEqual, 2) + convey.So(len(listRepos.JSON200.Results), convey.ShouldEqual, 2) }) c.Convey("list repository", func() { @@ -124,33 +124,33 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { listRepos, err := api.ParseListRepositoryResponse(resp) convey.So(err, convey.ShouldBeNil) - convey.So(len(*listRepos.JSON200), convey.ShouldEqual, 2) + convey.So(len(listRepos.JSON200.Results), convey.ShouldEqual, 2) }) c.Convey("list repository by prefix", func() { - resp, err := client.ListRepository(ctx, userName, &api.ListRepositoryParams{RepoPrefix: utils.String("happy")}) + resp, err := client.ListRepository(ctx, userName, &api.ListRepositoryParams{Prefix: utils.String("happy")}) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) listRepos, err := api.ParseListRepositoryResponse(resp) convey.So(err, convey.ShouldBeNil) - convey.So(len(*listRepos.JSON200), convey.ShouldEqual, 2) + convey.So(len(listRepos.JSON200.Results), convey.ShouldEqual, 2) }) c.Convey("list repository by prefix but found nothing", func() { - resp, err := client.ListRepository(ctx, userName, &api.ListRepositoryParams{RepoPrefix: utils.String("bad")}) + resp, err := client.ListRepository(ctx, userName, &api.ListRepositoryParams{Prefix: utils.String("bad")}) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) listRepos, err := api.ParseListRepositoryResponse(resp) convey.So(err, convey.ShouldBeNil) - convey.So(len(*listRepos.JSON200), convey.ShouldEqual, 0) + convey.So(len(listRepos.JSON200.Results), convey.ShouldEqual, 0) }) c.Convey("list others repository", func() { - resp, err := client.ListRepository(ctx, "admin", &api.ListRepositoryParams{RepoPrefix: utils.String("bad")}) + resp, err := client.ListRepository(ctx, "admin", &api.ListRepositoryParams{Prefix: utils.String("bad")}) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) }) diff --git a/models/repository.go b/models/repository.go index fa45739a..ee8118ce 100644 --- a/models/repository.go +++ b/models/repository.go @@ -58,6 +58,8 @@ type ListRepoParams struct { OwnerID uuid.UUID Name *string NameMatch MatchMode + After *string + Amount int } func NewListRepoParams() *ListRepoParams { @@ -84,6 +86,16 @@ func (lrp *ListRepoParams) SetCreatorID(creatorID uuid.UUID) *ListRepoParams { return lrp } +func (lrp *ListRepoParams) SetAfter(after string) *ListRepoParams { + lrp.After = &after + return lrp +} + +func (lrp *ListRepoParams) SetAmount(amount int) *ListRepoParams { + lrp.Amount = amount + return lrp +} + type DeleteRepoParams struct { ID uuid.UUID OwnerID uuid.UUID @@ -130,7 +142,7 @@ type IRepositoryRepo interface { Insert(ctx context.Context, repo *Repository) (*Repository, error) Get(ctx context.Context, params *GetRepoParams) (*Repository, error) - List(ctx context.Context, params *ListRepoParams) ([]*Repository, error) + List(ctx context.Context, params *ListRepoParams) ([]*Repository, bool, error) Delete(ctx context.Context, params *DeleteRepoParams) (int64, error) UpdateByID(ctx context.Context, updateModel *UpdateRepoParams) error } @@ -180,7 +192,7 @@ func (r *RepositoryRepo) Get(ctx context.Context, params *GetRepoParams) (*Repos return repo, nil } -func (r *RepositoryRepo) List(ctx context.Context, params *ListRepoParams) ([]*Repository, error) { +func (r *RepositoryRepo) List(ctx context.Context, params *ListRepoParams) ([]*Repository, bool, error) { repos := []*Repository{} query := r.db.NewSelect().Model(&repos) @@ -205,11 +217,13 @@ func (r *RepositoryRepo) List(ctx context.Context, params *ListRepoParams) ([]*R } } - err := query.Scan(ctx) - if err != nil { - return nil, err + query = query.Order("updated_at DESC") + if params.After != nil { + query = query.Where("updated_at < ?", *params.After) } - return repos, nil + + err := query.Limit(params.Amount).Scan(ctx) + return repos, len(repos) == params.Amount, err } func (r *RepositoryRepo) Delete(ctx context.Context, params *DeleteRepoParams) (int64, error) { From 3c8c0363628d06142e37573c9a05b199e1f124be Mon Sep 17 00:00:00 2001 From: zjy Date: Thu, 21 Dec 2023 09:04:13 +0800 Subject: [PATCH 099/210] feat: add branch list pagination --- api/jiaozifs.gen.go | 245 +++++++++++++++++++++++---------- api/swagger.yml | 4 + controller/branch_ctl.go | 56 +++++++- integrationtest/branch_test.go | 10 +- models/branch.go | 47 ++++++- models/branch_test.go | 4 +- models/repository_test.go | 12 +- 7 files changed, 277 insertions(+), 101 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index bdc40454..23acb632 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -343,6 +343,18 @@ type GetBranchParams struct { RefName string `form:"refName" json:"refName"` } +// ListBranchesParams defines parameters for ListBranches. +type ListBranchesParams struct { + // Prefix return items prefixed with this value + Prefix *PaginationPrefix `form:"prefix,omitempty" json:"prefix,omitempty"` + + // After return items after this value + After *PaginationAfter `form:"after,omitempty" json:"after,omitempty"` + + // Amount how many items to return + Amount *PaginationAmount `form:"amount,omitempty" json:"amount,omitempty"` +} + // GetCommitsInRepositoryParams defines parameters for GetCommitsInRepository. type GetCommitsInRepositoryParams struct { // RefName ref(branch/tag) name @@ -561,7 +573,7 @@ type ClientInterface interface { CreateBranch(ctx context.Context, owner string, repository string, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // ListBranches request - ListBranches(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) + ListBranches(ctx context.Context, owner string, repository string, params *ListBranchesParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetCommitsInRepository request GetCommitsInRepository(ctx context.Context, owner string, repository string, params *GetCommitsInRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -796,8 +808,8 @@ func (c *Client) CreateBranch(ctx context.Context, owner string, repository stri return c.Client.Do(req) } -func (c *Client) ListBranches(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewListBranchesRequest(c.Server, owner, repository) +func (c *Client) ListBranches(ctx context.Context, owner string, repository string, params *ListBranchesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListBranchesRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -1748,7 +1760,7 @@ func NewCreateBranchRequestWithBody(server string, owner string, repository stri } // NewListBranchesRequest generates requests for ListBranches -func NewListBranchesRequest(server string, owner string, repository string) (*http.Request, error) { +func NewListBranchesRequest(server string, owner string, repository string, params *ListBranchesParams) (*http.Request, error) { var err error var pathParam0 string @@ -1780,6 +1792,60 @@ func NewListBranchesRequest(server string, owner string, repository string) (*ht return nil, err } + if params != nil { + queryValues := queryURL.Query() + + if params.Prefix != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "prefix", runtime.ParamLocationQuery, *params.Prefix); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.After != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "after", runtime.ParamLocationQuery, *params.After); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Amount != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "amount", runtime.ParamLocationQuery, *params.Amount); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err @@ -2796,7 +2862,7 @@ type ClientWithResponsesInterface interface { CreateBranchWithResponse(ctx context.Context, owner string, repository string, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) // ListBranchesWithResponse request - ListBranchesWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*ListBranchesResponse, error) + ListBranchesWithResponse(ctx context.Context, owner string, repository string, params *ListBranchesParams, reqEditors ...RequestEditorFn) (*ListBranchesResponse, error) // GetCommitsInRepositoryWithResponse request GetCommitsInRepositoryWithResponse(ctx context.Context, owner string, repository string, params *GetCommitsInRepositoryParams, reqEditors ...RequestEditorFn) (*GetCommitsInRepositoryResponse, error) @@ -3613,8 +3679,8 @@ func (c *ClientWithResponses) CreateBranchWithResponse(ctx context.Context, owne } // ListBranchesWithResponse request returning *ListBranchesResponse -func (c *ClientWithResponses) ListBranchesWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*ListBranchesResponse, error) { - rsp, err := c.ListBranches(ctx, owner, repository, reqEditors...) +func (c *ClientWithResponses) ListBranchesWithResponse(ctx context.Context, owner string, repository string, params *ListBranchesParams, reqEditors ...RequestEditorFn) (*ListBranchesResponse, error) { + rsp, err := c.ListBranches(ctx, owner, repository, params, reqEditors...) if err != nil { return nil, err } @@ -4485,7 +4551,7 @@ type ServerInterface interface { CreateBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateBranchJSONRequestBody, owner string, repository string) // list branches // (GET /repos/{owner}/{repository}/branches) - ListBranches(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) + ListBranches(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params ListBranchesParams) // get commits in repository // (GET /repos/{owner}/{repository}/commits) GetCommitsInRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetCommitsInRepositoryParams) @@ -4613,7 +4679,7 @@ func (_ Unimplemented) CreateBranch(ctx context.Context, w *JiaozifsResponse, r // list branches // (GET /repos/{owner}/{repository}/branches) -func (_ Unimplemented) ListBranches(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) { +func (_ Unimplemented) ListBranches(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params ListBranchesParams) { w.WriteHeader(http.StatusNotImplemented) } @@ -5454,8 +5520,35 @@ func (siw *ServerInterfaceWrapper) ListBranches(w http.ResponseWriter, r *http.R ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context + var params ListBranchesParams + + // ------------- Optional query parameter "prefix" ------------- + + err = runtime.BindQueryParameter("form", true, false, "prefix", r.URL.Query(), ¶ms.Prefix) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "prefix", Err: err}) + return + } + + // ------------- Optional query parameter "after" ------------- + + err = runtime.BindQueryParameter("form", true, false, "after", r.URL.Query(), ¶ms.After) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "after", Err: err}) + return + } + + // ------------- Optional query parameter "amount" ------------- + + err = runtime.BindQueryParameter("form", true, false, "amount", r.URL.Query(), ¶ms.Amount) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "amount", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.ListBranches(r.Context(), &JiaozifsResponse{w}, r, owner, repository) + siw.Handler.ListBranches(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -6433,72 +6526,72 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 2b2931f1..4b472738 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -1296,6 +1296,10 @@ paths: - branches operationId: listBranches summary: list branches + parameters: + - $ref: "#/components/parameters/PaginationPrefix" + - $ref: "#/components/parameters/PaginationAfter" + - $ref: "#/components/parameters/PaginationAmount" responses: 200: description: branch list diff --git a/controller/branch_ctl.go b/controller/branch_ctl.go index f697cfbc..feeb8893 100644 --- a/controller/branch_ctl.go +++ b/controller/branch_ctl.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "reflect" "regexp" "strings" "time" @@ -21,6 +22,28 @@ import ( var MaxBranchNameLength = 40 var branchNameRegex = regexp.MustCompile("^[a-zA-Z0-9_]*$") +func paginationForBranches(hasMore bool, results interface{}, fieldName string) api.Pagination { + pagination := api.Pagination{ + HasMore: hasMore, + MaxPerPage: DefaultMaxPerPage, + } + if results == nil { + return pagination + } + if reflect.TypeOf(results).Kind() != reflect.Slice { + panic("results is not a slice") + } + s := reflect.ValueOf(results) + pagination.Results = s.Len() + if !hasMore || pagination.Results == 0 { + return pagination + } + v := s.Index(pagination.Results - 1) + token := v.FieldByName(fieldName) + pagination.NextOffset = token.String() + return pagination +} + func CheckBranchName(name string) error { for _, blackName := range RepoNameBlackList { if name == blackName { @@ -49,7 +72,7 @@ type BranchController struct { Repo models.IRepo } -func (bct BranchController) ListBranches(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string) { +func (bct BranchController) ListBranches(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.ListBranchesParams) { operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) @@ -73,14 +96,32 @@ func (bct BranchController) ListBranches(ctx context.Context, w *api.JiaozifsRes return } - branches, err := bct.Repo.BranchRepo().List(ctx, models.NewListBranchParams().SetRepositoryID(repository.ID)) + listBranchParams := models.NewListBranchParams() + if params.Prefix != nil && len(*params.Prefix) > 0 { + listBranchParams.SetName(*params.Prefix, models.PrefixMatch) + } + if params.After != nil { + listBranchParams.SetAfter(*params.After) + } + if params.Amount != nil { + i := *params.Amount + if i > DefaultMaxPerPage || i <= 0 { + listBranchParams.SetAmount(DefaultMaxPerPage) + } else { + listBranchParams.SetAmount(i) + } + } else { + listBranchParams.SetAmount(DefaultMaxPerPage) + } + + branches, hasMore, err := bct.Repo.BranchRepo().List(ctx, listBranchParams.SetRepositoryID(repository.ID)) if err != nil { w.Error(err) return } - var apiBranches []api.Branch + results := make([]api.Branch, 0, len(branches)) for _, branch := range branches { - branch := api.Branch{ + r := api.Branch{ CommitHash: branch.CommitHash.Hex(), CreatedAt: branch.CreatedAt, CreatorID: branch.CreatorID, @@ -90,9 +131,12 @@ func (bct BranchController) ListBranches(ctx context.Context, w *api.JiaozifsRes RepositoryID: branch.RepositoryID, UpdatedAt: branch.UpdatedAt, } - apiBranches = append(apiBranches, branch) + results = append(results, r) } - w.JSON(api.BranchList{Results: apiBranches}) + w.JSON(api.BranchList{ + Pagination: paginationForBranches(hasMore, results, "Name"), + Results: results, + }) } func (bct BranchController) CreateBranch(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.CreateBranchJSONRequestBody, ownerName string, repositoryName string) { diff --git a/integrationtest/branch_test.go b/integrationtest/branch_test.go index 660c670c..417ae403 100644 --- a/integrationtest/branch_test.go +++ b/integrationtest/branch_test.go @@ -154,14 +154,14 @@ func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors client.RequestEditors = nil - resp, err := client.ListBranches(ctx, userName, repoName) + resp, err := client.ListBranches(ctx, userName, repoName, &api.ListBranchesParams{}) client.RequestEditors = re convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("success list branch", func() { - resp, err := client.ListBranches(ctx, userName, repoName) + resp, err := client.ListBranches(ctx, userName, repoName, &api.ListBranchesParams{}) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) @@ -171,19 +171,19 @@ func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("fail to list branchs from non exit user", func() { - resp, err := client.ListBranches(ctx, "mock_owner", repoName) + resp, err := client.ListBranches(ctx, "mock_owner", repoName, &api.ListBranchesParams{}) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) }) c.Convey("fail to list branchs in non exit repo", func() { - resp, err := client.ListBranches(ctx, userName, "mockrepo") + resp, err := client.ListBranches(ctx, userName, "mockrepo", &api.ListBranchesParams{}) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) }) c.Convey("fail to list branches in others repo", func() { - resp, err := client.ListBranches(ctx, "jimmy", "happygo") + resp, err := client.ListBranches(ctx, "jimmy", "happygo", &api.ListBranchesParams{}) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) }) diff --git a/models/branch.go b/models/branch.go index 4735fa47..271d5b91 100644 --- a/models/branch.go +++ b/models/branch.go @@ -93,6 +93,10 @@ func (up *UpdateBranchParams) SetCommitHash(commitHash hash.Hash) *UpdateBranchP type ListBranchParams struct { RepositoryID uuid.UUID + Name *string + NameMatch MatchMode + After *string + Amount int } func NewListBranchParams() *ListBranchParams { @@ -104,12 +108,28 @@ func (gup *ListBranchParams) SetRepositoryID(repositoryID uuid.UUID) *ListBranch return gup } +func (gup *ListBranchParams) SetName(name string, match MatchMode) *ListBranchParams { + gup.Name = &name + gup.NameMatch = match + return gup +} + +func (gup *ListBranchParams) SetAfter(after string) *ListBranchParams { + gup.After = &after + return gup +} + +func (gup *ListBranchParams) SetAmount(amount int) *ListBranchParams { + gup.Amount = amount + return gup +} + type IBranchRepo interface { Insert(ctx context.Context, repo *Branches) (*Branches, error) UpdateByID(ctx context.Context, params *UpdateBranchParams) error Get(ctx context.Context, id *GetBranchParams) (*Branches, error) - List(ctx context.Context, params *ListBranchParams) ([]*Branches, error) + List(ctx context.Context, params *ListBranchParams) ([]*Branches, bool, error) Delete(ctx context.Context, params *DeleteBranchParams) (int64, error) } @@ -154,7 +174,7 @@ func (r BranchRepo) Get(ctx context.Context, params *GetBranchParams) (*Branches return repo, nil } -func (r BranchRepo) List(ctx context.Context, params *ListBranchParams) ([]*Branches, error) { +func (r BranchRepo) List(ctx context.Context, params *ListBranchParams) ([]*Branches, bool, error) { var branches []*Branches query := r.db.NewSelect().Model(&branches) @@ -162,11 +182,26 @@ func (r BranchRepo) List(ctx context.Context, params *ListBranchParams) ([]*Bran query = query.Where("repository_id = ?", params.RepositoryID) } - err := query.Scan(ctx) - if err != nil { - return nil, err + if params.Name != nil { + switch params.NameMatch { + case ExactMatch: + query = query.Where("name = ?", *params.Name) + case PrefixMatch: + query = query.Where("name LIKE ?", *params.Name+"%") + case SuffixMatch: + query = query.Where("name LIKE ?", "%"+*params.Name) + case LikeMatch: + query = query.Where("name LIKE ?", "%"+*params.Name+"%") + } + } + + query = query.Order("name ASC") + if params.After != nil { + query = query.Where("name > ?", *params.After) } - return branches, nil + + err := query.Limit(params.Amount).Scan(ctx) + return branches, len(branches) == params.Amount, err } func (r BranchRepo) Delete(ctx context.Context, params *DeleteBranchParams) (int64, error) { diff --git a/models/branch_test.go b/models/branch_test.go index 244219f0..f74496e2 100644 --- a/models/branch_test.go +++ b/models/branch_test.go @@ -46,7 +46,7 @@ func TestRefRepoInsert(t *testing.T) { require.NoError(t, err) require.Equal(t, mockHash, branchAfterUpdated.CommitHash) - list, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID)) + list, _, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID)) require.NoError(t, err) require.Len(t, list, 1) @@ -54,7 +54,7 @@ func TestRefRepoInsert(t *testing.T) { require.NoError(t, err) require.Equal(t, int64(1), affectedRows) - list, err = repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID)) + list, _, err = repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID)) require.NoError(t, err) require.Len(t, list, 0) } diff --git a/models/repository_test.go b/models/repository_test.go index bc8b411e..f1243a96 100644 --- a/models/repository_test.go +++ b/models/repository_test.go @@ -46,38 +46,38 @@ func TestRepositoryRepo_Insert(t *testing.T) { require.NotEqual(t, uuid.Nil, secRepo.ID) //list - repos, err := repo.List(ctx, models.NewListRepoParams()) + repos, _, err := repo.List(ctx, models.NewListRepoParams()) require.NoError(t, err) require.Len(t, repos, 2) { //exact adabbeb - repos, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("adabbeb", models.PrefixMatch)) + repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("adabbeb", models.PrefixMatch)) require.NoError(t, err) require.Len(t, repos, 1) } { //prefix a - repos, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("a", models.PrefixMatch)) + repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("a", models.PrefixMatch)) require.NoError(t, err) require.Len(t, repos, 2) } { //subfix b - repos, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("b", models.SuffixMatch)) + repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("b", models.SuffixMatch)) require.NoError(t, err) require.Len(t, repos, 2) } { //like ab - repos, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("ab", models.LikeMatch)) + repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("ab", models.LikeMatch)) require.NoError(t, err) require.Len(t, repos, 2) } { //like ab - repos, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("adabbeb", models.LikeMatch)) + repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("adabbeb", models.LikeMatch)) require.NoError(t, err) require.Len(t, repos, 1) } From e48ab538ffa6bfcb9be5da8d59a2c338421c903f Mon Sep 17 00:00:00 2001 From: zjy Date: Thu, 21 Dec 2023 09:17:02 +0800 Subject: [PATCH 100/210] feat: add branch and repo models test --- models/branch_test.go | 25 ++++++++++++++++++++++++- models/repository_test.go | 21 +++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/models/branch_test.go b/models/branch_test.go index f74496e2..5956e136 100644 --- a/models/branch_test.go +++ b/models/branch_test.go @@ -50,11 +50,34 @@ func TestRefRepoInsert(t *testing.T) { require.NoError(t, err) require.Len(t, list, 1) + // second + secModel := &models.Branches{} + require.NoError(t, gofakeit.Struct(secModel)) + secModel.RepositoryID = branch.RepositoryID + secRef, err := repo.Insert(ctx, secModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, secRef.ID) + + getSecRefParams := models.NewGetBranchParams(). + SetID(secRef.ID). + SetRepositoryID(secRef.RepositoryID). + SetName(secRef.Name) + sRef, err := repo.Get(ctx, getSecRefParams) + require.NoError(t, err) + + require.True(t, cmp.Equal(secModel, sRef, dbTimeCmpOpt)) + + // amount + list, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetAmount(1)) + require.NoError(t, err) + require.Len(t, list, 1) + require.True(t, hasMore) + affectedRows, err := repo.Delete(ctx, models.NewDeleteBranchParams().SetID(list[0].ID).SetRepositoryID(list[0].RepositoryID).SetName(list[0].Name)) require.NoError(t, err) require.Equal(t, int64(1), affectedRows) list, _, err = repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID)) require.NoError(t, err) - require.Len(t, list, 0) + require.Len(t, list, 1) } diff --git a/models/repository_test.go b/models/repository_test.go index f1243a96..d6315a4c 100644 --- a/models/repository_test.go +++ b/models/repository_test.go @@ -81,6 +81,27 @@ func TestRepositoryRepo_Insert(t *testing.T) { require.NoError(t, err) require.Len(t, repos, 1) } + { + //amount 1 + repos, hasMore, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetAmount(1)) + require.NoError(t, err) + require.True(t, hasMore) + require.Len(t, repos, 1) + } + { + //amount 2 + repos, hasMore, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetAmount(2)) + require.NoError(t, err) + require.True(t, hasMore) + require.Len(t, repos, 2) + } + { + //amount 3 + repos, hasMore, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetAmount(3)) + require.NoError(t, err) + require.False(t, hasMore) + require.Len(t, repos, 2) + } //delete deleteParams := models.NewDeleteRepoParams(). SetID(secRepo.ID). From 1bdb6508fbbafa35c597e36d510d16fcfcafd17e Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Thu, 21 Dec 2023 10:18:36 +0800 Subject: [PATCH 101/210] chore: use ref type to replace iswip --- api/jiaozifs.gen.go | 250 ++++++++++++++++------------- api/resp.gen.go | 1 + api/swagger.yml | 24 +-- controller/commit_ctl.go | 25 ++- controller/object_ctl.go | 48 ++++-- integrationtest/commit_test.go | 67 +++++--- integrationtest/objects_test.go | 37 ++++- integrationtest/wip_object_test.go | 38 ++--- 8 files changed, 293 insertions(+), 197 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 435d3fc9..6a9c7ff0 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -42,6 +42,14 @@ const ( Simplified LoginConfigRBAC = "simplified" ) +// Defines values for RefType. +const ( + RefTypeBranch RefType = "branch" + RefTypeTag RefType = "tag" + RefTypeTest RefType = "test" + RefTypeWip RefType = "wip" +) + // Defines values for SetupStateState. const ( Initialized SetupStateState = "initialized" @@ -175,6 +183,9 @@ type Pagination struct { Results int `json:"results"` } +// RefType defines model for RefType. +type RefType string + // Repository defines model for Repository. type Repository struct { CreatedAt time.Time `json:"CreatedAt"` @@ -275,8 +286,8 @@ type DeleteObjectParams struct { // GetObjectParams defines parameters for GetObject. type GetObjectParams struct { - // IsWip isWip indicate to retrieve from working in progress, default false - IsWip *bool `form:"isWip,omitempty" json:"isWip,omitempty"` + // Type type indicate to retrieve from wip/branch/tag, default branch + Type RefType `form:"type" json:"type"` // RefName branch/tag to the ref RefName string `form:"refName" json:"refName"` @@ -290,8 +301,8 @@ type GetObjectParams struct { // HeadObjectParams defines parameters for HeadObject. type HeadObjectParams struct { - // IsWip isWip indicate to retrieve from working in progress, default false - IsWip *bool `form:"isWip,omitempty" json:"isWip,omitempty"` + // Type type indicate to retrieve from wip/branch/tag, default branch + Type RefType `form:"type" json:"type"` // RefName branch/tag to the ref RefName string `form:"refName" json:"refName"` @@ -348,8 +359,8 @@ type GetEntriesInRefParams struct { // Ref specific branch, default to repostiory default branch(HEAD) Ref *string `form:"ref,omitempty" json:"ref,omitempty"` - // IsWip isWip indicate to retrieve from working in progress, default false - IsWip *bool `form:"isWip,omitempty" json:"isWip,omitempty"` + // Type type indicate to retrieve from wip/branch/tag, default branch + Type RefType `form:"type" json:"type"` } // ListRepositoryParams defines parameters for ListRepository. @@ -1164,20 +1175,16 @@ func NewGetObjectRequest(server string, owner string, repository string, params if params != nil { queryValues := queryURL.Query() - if params.IsWip != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "isWip", runtime.ParamLocationQuery, *params.IsWip); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "type", runtime.ParamLocationQuery, params.Type); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) } } - } if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { @@ -1266,20 +1273,16 @@ func NewHeadObjectRequest(server string, owner string, repository string, params if params != nil { queryValues := queryURL.Query() - if params.IsWip != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "isWip", runtime.ParamLocationQuery, *params.IsWip); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "type", runtime.ParamLocationQuery, params.Type); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) } } - } if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { @@ -1955,20 +1958,16 @@ func NewGetEntriesInRefRequest(server string, owner string, repository string, p } - if params.IsWip != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "isWip", runtime.ParamLocationQuery, *params.IsWip); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "type", runtime.ParamLocationQuery, params.Type); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) } } - } queryURL.RawQuery = queryValues.Encode() @@ -4755,11 +4754,18 @@ func (siw *ServerInterfaceWrapper) GetObject(w http.ResponseWriter, r *http.Requ // Parameter object where we will unmarshal all parameters from the context var params GetObjectParams - // ------------- Optional query parameter "isWip" ------------- + // ------------- Required query parameter "type" ------------- + + if paramValue := r.URL.Query().Get("type"); paramValue != "" { - err = runtime.BindQueryParameter("form", true, false, "isWip", r.URL.Query(), ¶ms.IsWip) + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "type"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "type", r.URL.Query(), ¶ms.Type) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "isWip", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "type", Err: err}) return } @@ -4858,11 +4864,18 @@ func (siw *ServerInterfaceWrapper) HeadObject(w http.ResponseWriter, r *http.Req // Parameter object where we will unmarshal all parameters from the context var params HeadObjectParams - // ------------- Optional query parameter "isWip" ------------- + // ------------- Required query parameter "type" ------------- - err = runtime.BindQueryParameter("form", true, false, "isWip", r.URL.Query(), ¶ms.IsWip) + if paramValue := r.URL.Query().Get("type"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "type"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "type", r.URL.Query(), ¶ms.Type) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "isWip", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "type", Err: err}) return } @@ -5507,11 +5520,18 @@ func (siw *ServerInterfaceWrapper) GetEntriesInRef(w http.ResponseWriter, r *htt return } - // ------------- Optional query parameter "isWip" ------------- + // ------------- Required query parameter "type" ------------- + + if paramValue := r.URL.Query().Get("type"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "type"}) + return + } - err = runtime.BindQueryParameter("form", true, false, "isWip", r.URL.Query(), ¶ms.IsWip) + err = runtime.BindQueryParameter("form", true, true, "type", r.URL.Query(), ¶ms.Type) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "isWip", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "type", Err: err}) return } @@ -6268,70 +6288,70 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "HQAA//+t9VIHoF4AAA==", + "H4sIAAAAAAAC/+xc63PbNrb/VzC8/ZDcq5cfzdyq0+nEjtN410k9ttN8iL0aiDyUkJAAC4CW1Yz/9x0A", + "fBOkKEW25e5+8VgkHuf5wzkHAL85LgsjRoFK4Yy/OcKdQ4j1v69jOQcqiYslYfSKfQWqHkecRcAlAd1I", + "po89EC4nkWrqjB2M/vHpCumXSM6xRC6LAw9NAcUCPCQZwvnogDj8GYOQwuk5chmBM3aE5ITOnPuemWEC", + "dxHh2IxenewjJXfoJGLuHBGKBLiMemoon/EQS2fsECpfHeZjEyphBty5v+85ambCwXPGnxNebrJ2bPoF", + "XKloOOKYuvM698csDIl8h4V+VyP9mAOW4L2W6m1GjYcl9CUJwcat7sL46ZtSlzgmnq31m6IcLAR0HOYD", + "DsHa/wIiJohkfNlxpI+Rtx7HFRWcvnEqs/aKQk5ILYqpKOXi/M1q1O0TiZXVSZvkIFjMXdurCvnUUJc0", + "bybhjAhZnz7CM0Iz0n7g4Dtj53+GuYMOE+8cnuctNQUiDoz7EgmhWNU7seb7jDzMOV7WmCmQk89h4+l4", + "jukM6vy8dlNegMahM/6819vvHdzU/bDnHGEBjW50jqX9xRVr6FPhRA/QS+mxsqBtzMJCLOeMrxLoJZlR", + "LGMO+VAS1uy1PlQ0yus98Blc4VnDSyHwDBoEzYFqT4OyNdVBuWQ4GwDFFYdmhX8viiRYcaUa1eAkUWlR", + "UQWR5QIq0FiRzDqQY1rmJNRNbBWGN4BzhWXdykbAGZsResyoT2b1uS+OXh/X11P1FC1IECAOISYUAcXT", + "ADzEKPrt4ykiPrp24E4Cpzi4dgYIXaklntFgiRaMfxXXdEHkHGGK0lZ6uUcC+C1xYXCtECXBBEeQMAqI", + "T0DZSdq+wEouCR8HwRS7XyeB4mkS4CkEder1YxVhRAF2QdFc6RfzYOCsHj7mlsFNcIH5En28OFOTMN8H", + "roIaLtTPWADyGUd6COssZnCXsa8EJmq9EPVZzFuk32YBk5CMgwqrnN4ajmmm8zEJwJuEue+XJ0xeqGk8", + "IqIALxNmuECLOUOqv3qiR/sZYeTHQYAEUAnUBRPhEYE4UA84eNeUUPTu6v0ZwtRDIV4il1GpLAmjgNCv", + "Ov5DuSz1sCgEOWeeto0GqVlVEnESFhTSSQMslvbB6oPMCJ0hFsvBStTJabRquTSxzVN/1/9dSmyW8rKn", + "unNwvwrlMRalK+kClRPzosqTGReF4BGMpMHE2hAhSOxhiVctWmawjwL4+7SH6q1heXuBec+JmtZ89WIS", + "Mg/Kywyh8mDfOpIgf8FkupRGjuvmBJncE5ISAhIxGr6blVmS0/ibgz2PKNng4LycRTW4cT7eeSk2LNvG", + "HItJyLhFAR/gTqJIeTYRCN9iEiggz7meMhYA1kFkiO8mEfBJZAWI9/iOhDhANA6nwBHzEVDJCQgUAdcz", + "KGkQSkJloiObHijcyQnzfQGyPr7OLjOo46DGvlXAAoimPNjMthD6VjjPCL3FQQwC+SymnjJDNWbarZ3m", + "iilkYq4IK6eizKTNLC7Av0qcNF3/piYa7zkLEikWdRAiQUjrGtgWSTx5uvkOsPcgeehW0sokddREbppB", + "XoKMIwXRlnTHZWE4iTj4YhISIRQdNbOUPAYVPykjVO2Rbo8wB5T0GVi9M11P0jCuDaKLEZ/Cv5Ta1OAI", + "JZLggPylIy7K5KT45MYmy7ocstylJoaTEJOgpCfQT9bR96e5KSptoOpEyyfJnHokmyZVcH9Cpc2PGvOS", + "U/GG8MKbgoKao/TazMbCNk8JrGMK4KfUZxarXB8U3JirbEfp+JRu3PH0vLzeRreHtj7Q3VwCLDYgKu/V", + "kaJY62edKVSgTDulaVnLlPGbBmVewIwI2aTUNYQWYSEWjGtcDgk9AzpTkdX/b5eNwjw2jv4ALgijF3qd", + "rLODIzK5NU3qkMljquSO0gZWFav1sjhErUnj8BFnM47D5uErrOftilTbmP5EojqrR1hAXmx6/IrxsXFR", + "hX67UTHOFtNiZG4N5DcJAipKUcshuDEncnmpVkujkykWxJ3g2GQcehnV6K4e56POpYxMsqWTurQ5yRN2", + "tZpquWiqOcWBbjURIMqmhSPyT9Dp+ZeFnGRbKFPAHPjblDOT6ufk6LdVehRLJMGIsmF/IZj9RXyB3l1d", + "naPX56cqAyUuUAF5rdt5HWF3Dmh/MHJ6jk6J9cBiPBwuFosB1q8HjM+GSV8xPDs9PvlwedLfH4wGcxkG", + "Ol8hMoDipGa+zOucvcFoMFItWQQUR8QZOwf6kUmotB6GSlpDHepox2GmSK7cR2c+p54zNvUsx/gkCHnE", + "vKUJvnQKbNAkCpJNq+EXYXzexEa2knuOjtvBwxYcvDfdRMSUHNWo+6PRWsS3hX227To9Y6WCFbsuCOHH", + "gSmROD1nDtgDrgm6BNk/NsZcmhjucBgFjab9C566HuztH/z46md0juX8l+HP6J2U0e80WFocU5F1ONqz", + "VQywLs+qUBT9gQPiaW5OOGcaAw73R5agmjEUYrrMtxE11z5OFpty69OEAXQJ/BY4SsYuQIMz/nzTc0Qc", + "hlhFZ04EXMENwpnEJJ4JpXcNAjeqb2a7LJatxqve262gTU+q127KzC4lw6VFTMYXht/YggK/H37j2Xpx", + "b6YNwCwHZcG90c9NUaUuvsM6xWYeZMbzUC7NYNlJkKbRQb3RW8anxPOAmhaWqT8w+ZbF1FtH9iVJGqKR", + "YWGA3pvEMPktTGWeMok4yJhThFE6IwKll0FB8kkf5+a+58zAYpG/gcykGmGOQ5AaCj7XiF5GgAj1zIZ9", + "sUjjcxaiBYmGppIxlHjWQ4kpoay6odfJP2Pgy3yZTKpoOZKq9LjXEe/SUsr9fa9K69FSAuKYzkqE6u2F", + "FMZ0QfCXUX9vtH+QUmdwMCfvQm9rFumJsFSO4Iydf5kBXry4vvb+t6/+9H5Fv778v5c/WODuZi3YZ64E", + "2ReSAw7LKJwFPVNCMbcCa8/uB+lUJbA/Ng/7aU5gnaqlTnqS7DHmveqr5BkWsv+eeWaDp7Wxar4/evVY", + "kokwlwQH6CEllPa/SDfIv9uUHkTqB6N9yy4geIQryejNmohDX5AZBU9vtPiM6xoWS7GjILQz5maF6vZ5", + "O6JwM7wrFPQzrN0bNTbUx4eS8fZe2ZjVSAwe0qpSiIousSTCJ7pivimUz0DWDcwGzvOkdFpG53eAvf/C", + "8xPBc4MhEXNO7VngaBfEQzp9/E+Evb8l/LRE8Wnqps9hADfRYgWw9H4nIj6q2rsNtCqIRIyR6V3SxEd1", + "mN+KITUtWsfJ04R1ByuLIMdABT1mK9BvgD8OfrKZ8B0TcgiwJLewerqE4e5z3fQassyPUcAK60a3Ssn3", + "xFY9J4wDSRS+DFXrfrrf3VR2KdBQOatAgyXCSOU7ASCfBKA3mGPNEVrMiTtHYSwkmprjMR66Tge7dgbF", + "owUtxHYoy+xtrSxTPNXRHJ+HhcMUh7blx5bXb1QM2CynjXlQRTtLyHjO9REPfcThrT5ytGbgVAOZnnPX", + "v8246MOdG8Qe9KfalpWD6KKCRocNawoXZWTpWJbRJ6VMml6Apm0qr4uybFWDElKm8lQPW2sAK6WwFV8o", + "zGJxBRUr74owK7RYJLnza1/L8lDZf968mN6m7No09+WqufbeNV3ObM3ujJXUyakZSjs8JTnZapQ6SvM0", + "m9ltIW656VJTNcSmuLdhSfXQFv2aCxM67G0vnV5tvWydcJMlwqn+zANoL50+vlq2B8bpLZA6EE+z+yHP", + "VKcKvdsV+nzR25wQyAzvIZC7ck2qE27vPejslePxWgSJhlMcWm8l6G6xo59amh4z6gfElQJ9InKOrjBX", + "SPF4hl6ShN3WOy1ARotWlDsjIoE5fYb9geFIX4xrhCQU6NfPFpcU+WiaC/OZQtMKk3L18aZmi/oNpDkB", + "JU5pKQptLXBz8F/k1ZuXKDlu0b7SPtzK2umiZXLQq37R0pr7pHKrr2XJG0Tos09KVttOhDkMv02xgDlg", + "7361Gb0hvr/KekQELvGJixQXPUR8Xc3IniY76emdBiVnxmR7oe6pbctcuu1gW8Z6kKfEtO106UdbupSU", + "l7NyMzSEaAXCgAN1S5hoXj6bMrNlsNSEt+wg2ojEsM0vTowdK3jdLc/oNc5uoL2Hsp1JvTOoQmDC+LKy", + "X/ni3cnrNy+b0X89GnZ46/RRkCS/CdEZTB697NKtLF0Ptop2q+3iOcKLxgQBMo7anL5wNekBw/TCLBbr", + "yI7/amqRuXq01uZl44JCXEAxze8wth3YzDYxy/RU1M9okh7pe85Dnty4aD69md7JeKiCafXaR/POVDU0", + "Vp0MoSvT4SPsoWS7GfULO0RoN87YKl2gIkP2Y6SpyiLWnrnm+cXvfuGANHhK2M5joOtFqVa9Cl41aO1K", + "ebtKjC3haKlRPfgOQ22aB6hUPYCOKSx2RsVJAWnVDoZxN/W3bQXKLiE+4PqTzWER7GV+ZF6fu/MNmpjm", + "3y89E4d8d2GaUHNGQWEuS24Cm0tZgf4Sxgy8PtF34Xkb9qXZwToY2HWjImLnHHxy9/RZ7nqelZtxoVK4", + "I+ipv4CRZjtpQPkoBRwdPhbuPza57x/ZzcYH897yPVDboezKbcy2kCFJTdMumHrIclfUFvCplG6z0yKf", + "9DcaNjkmsiDR09ojh5Ddgv5AE6EzZY4RZzpUzKWkiGzb72xmfyvmoYa3GIWF5Cc/HNJJjKu9easFp43S", + "1FqVvVtlfWs7mVaT2nt8k0LJhxKeoLTxo+36RafDuiZ662CLbag3dHUpuXXD5hOJjpNWq/dptm1BvfpB", + "djn/mxTnbYaYCHoHMS6jbROs24UqWrMP5B8AfXaH2h8VtLWcDGi34kCyuRNmX9O0kRaK2c6ch2pYKRI+", + "0oBOR5kJCUh/5VKl808R3H3PumF4svi3ZPXDJB1WkCD5nnFjDrqFwLET8H4yilgfdXcgV1yQqJQkRpzp", + "uwHK5CrVgGcEuuUE7lvxiyefb9Rkxa+vmCelL6x8vlFeb4zZhjOFEr+xd+pFzHxCJv+cyXg4DJiLgzkT", + "cnxw+NPewRBHZHi759TRdOWAWdeb+38HAAD//2+n6vo+XwAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/resp.gen.go b/api/resp.gen.go index b726ce38..e66c7ab1 100644 --- a/api/resp.gen.go +++ b/api/resp.gen.go @@ -5,6 +5,7 @@ // // mockgen --package=api --destination=resp.gen.go net/http ResponseWriter // + // Package api is a generated GoMock package. package api diff --git a/api/swagger.yml b/api/swagger.yml index 21503ea6..e0e9acc1 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -121,6 +121,9 @@ components: type: string source: type: string + RefType: + type: string + enum: ["branch", "wip","tag", "test"] Branch: type: object required: @@ -608,10 +611,11 @@ paths: summary: get object content parameters: - in: query - name: isWip - description: isWip indicate to retrieve from working in progress, default false + name: type + description: type indicate to retrieve from wip/branch/tag, default branch + required: true schema: - type: boolean + $ref: "#/components/schemas/RefType" - in: header name: Range description: Byte range to retrieve @@ -684,10 +688,11 @@ paths: summary: check if object exists parameters: - in: query - name: isWip - description: isWip indicate to retrieve from working in progress, default false + name: type + description: type indicate to retrieve from wip/branch/tag, default branch + required: true schema: - type: boolean + $ref: "#/components/schemas/RefType" - in: header name: Range description: Byte range to retrieve @@ -1021,10 +1026,11 @@ paths: schema: type: string - in: query - name: isWip - description: isWip indicate to retrieve from working in progress, default false + name: type + description: type indicate to retrieve from wip/branch/tag, default branch + required: true schema: - type: boolean + $ref: "#/components/schemas/RefType" responses: 200: description: commit diff --git a/controller/commit_ctl.go b/controller/commit_ctl.go index 3670376d..0435ad7b 100644 --- a/controller/commit_ctl.go +++ b/controller/commit_ctl.go @@ -50,26 +50,31 @@ func (commitCtl CommitController) GetEntriesInRef(ctx context.Context, w *api.Ji refName = *params.Ref } - ref, err := commitCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(refName)) - if err != nil { - w.Error(err) - return - } - if operator.Name != ownerName { //todo check permission w.Forbidden() return } treeHash := hash.EmptyHash - if utils.BoolValue(params.IsWip) { + if params.Type == api.RefTypeWip { + //todo maybe from tag reference + ref, err := commitCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(refName)) + if err != nil { + w.Error(err) + return + } wip, err := commitCtl.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetCreatorID(operator.ID).SetRepositoryID(repository.ID).SetRefID(ref.ID)) if err != nil { w.Error(err) return } treeHash = wip.CurrentTree - } else { + } else if params.Type == api.RefTypeBranch { + ref, err := commitCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(refName)) + if err != nil { + w.Error(err) + return + } if !ref.CommitHash.IsEmpty() { commit, err := commitCtl.Repo.CommitRepo(repository.ID).Commit(ctx, ref.CommitHash) if err != nil { @@ -78,6 +83,10 @@ func (commitCtl CommitController) GetEntriesInRef(ctx context.Context, w *api.Ji } treeHash = commit.TreeHash } + } else { + //check in validate middleware, test cant cover here, keep this check + w.BadRequest("not support") + return } workTree, err := versionmgr.NewWorkTree(ctx, commitCtl.Repo.FileTreeRepo(repository.ID), models.NewRootTreeEntry(treeHash)) diff --git a/controller/object_ctl.go b/controller/object_ctl.go index 2ebe598c..a65be493 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -125,22 +125,25 @@ func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsRespon return } - ref, err := oct.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(params.RefName)) - if err != nil { - w.Error(err) - return - } - treeHash := hash.EmptyHash - if utils.BoolValue(params.IsWip) { + if params.Type == "wip" { + ref, err := oct.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(params.RefName)) + if err != nil { + w.Error(err) + return + } wip, err := oct.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetCreatorID(operator.ID).SetRepositoryID(repository.ID).SetRefID(ref.ID)) if err != nil { w.Error(err) return } treeHash = wip.CurrentTree - } else { - + } else if params.Type == "branch" { + ref, err := oct.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(params.RefName)) + if err != nil { + w.Error(err) + return + } if !ref.CommitHash.IsEmpty() { commit, err := oct.Repo.CommitRepo(repository.ID).Commit(ctx, ref.CommitHash) if err != nil { @@ -149,6 +152,9 @@ func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsRespon } treeHash = commit.TreeHash } + } else { + w.BadRequest("not support type") + return } workTree, err := versionmgr.NewWorkTree(ctx, oct.Repo.FileTreeRepo(repository.ID), models.NewRootTreeEntry(treeHash)) @@ -231,22 +237,27 @@ func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsRespo w.Error(err) return } - ref, err := oct.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(params.RefName)) - if err != nil { - w.Error(err) - return - } treeHash := hash.EmptyHash - if utils.BoolValue(params.IsWip) { + if params.Type == "wip" { + ref, err := oct.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(params.RefName)) + if err != nil { + w.Error(err) + return + } + wip, err := oct.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetCreatorID(operator.ID).SetRepositoryID(repository.ID).SetRefID(ref.ID)) if err != nil { w.Error(err) return } treeHash = wip.CurrentTree - } else { - + } else if params.Type == "branch" { + ref, err := oct.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(params.RefName)) + if err != nil { + w.Error(err) + return + } if !ref.CommitHash.IsEmpty() { commit, err := oct.Repo.CommitRepo(repository.ID).Commit(ctx, ref.CommitHash) if err != nil { @@ -255,6 +266,9 @@ func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsRespo } treeHash = commit.TreeHash } + } else { + w.BadRequest("not support type") + return } fileRepo := oct.Repo.FileTreeRepo(repository.ID) diff --git a/integrationtest/commit_test.go b/integrationtest/commit_test.go index 0720c0c8..72c0d5f3 100644 --- a/integrationtest/commit_test.go +++ b/integrationtest/commit_test.go @@ -33,9 +33,9 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { re := client.RequestEditors client.RequestEditors = nil resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ - Path: utils.String("g"), - Ref: utils.String(branchName), - IsWip: utils.Bool(true), + Path: utils.String("g"), + Ref: utils.String(branchName), + Type: api.RefTypeWip, }) client.RequestEditors = re convey.So(err, convey.ShouldBeNil) @@ -44,9 +44,9 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to get entries in non exit user", func() { resp, err := client.GetEntriesInRef(ctx, "mock user", repoName, &api.GetEntriesInRefParams{ - Path: utils.String("g"), - Ref: utils.String(branchName), - IsWip: utils.Bool(true), + Path: utils.String("g"), + Ref: utils.String(branchName), + Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -54,9 +54,9 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to get entries in non exit repo", func() { resp, err := client.GetEntriesInRef(ctx, userName, "fakerepo", &api.GetEntriesInRefParams{ - Path: utils.String("g"), - Ref: utils.String(branchName), - IsWip: utils.Bool(true), + Path: utils.String("g"), + Ref: utils.String(branchName), + Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -64,9 +64,9 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to get entries in non exit branch", func() { resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ - Path: utils.String("g"), - Ref: utils.String("feat/fake_repo"), - IsWip: utils.Bool(true), + Path: utils.String("g"), + Ref: utils.String("feat/fake_repo"), + Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -74,9 +74,9 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("forbidden get entries in others", func() { resp, err := client.GetEntriesInRef(ctx, "jimmy", "happygo", &api.GetEntriesInRefParams{ - Path: utils.String("g"), - Ref: utils.String("main"), - IsWip: utils.Bool(true), + Path: utils.String("g"), + Ref: utils.String("main"), + Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) @@ -84,19 +84,18 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("not exit path", func() { resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ - Path: utils.String("a/b/c/d"), - Ref: utils.String(branchName), - IsWip: utils.Bool(true), + Path: utils.String("a/b/c/d"), + Ref: utils.String(branchName), + Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) }) - c.Convey("success to get entries", func() { resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ - Path: utils.String("g"), - Ref: utils.String(branchName), - IsWip: utils.Bool(true), + Path: utils.String("g"), + Ref: utils.String(branchName), + Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) @@ -110,9 +109,9 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("success to get entries in root", func() { resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ - Path: utils.String("/"), - Ref: utils.String(branchName), - IsWip: utils.Bool(true), + Path: utils.String("/"), + Ref: utils.String(branchName), + Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) @@ -127,6 +126,16 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { commitWip(ctx, c, client, "commit kitty first changes", userName, repoName, branchName, "test") + c.Convey("get entries with no type", func() { + resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ + Path: utils.String("g"), + Ref: utils.String(branchName), + Type: api.RefTypeTest, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + c.Convey("get branch entries", func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors @@ -134,6 +143,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ Path: utils.String("g"), Ref: utils.String(branchName), + Type: api.RefTypeBranch, }) client.RequestEditors = re convey.So(err, convey.ShouldBeNil) @@ -144,6 +154,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.GetEntriesInRef(ctx, "mock user", repoName, &api.GetEntriesInRefParams{ Path: utils.String("g"), Ref: utils.String(branchName), + Type: api.RefTypeBranch, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -153,6 +164,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.GetEntriesInRef(ctx, userName, "fakerepo", &api.GetEntriesInRefParams{ Path: utils.String("g"), Ref: utils.String(branchName), + Type: api.RefTypeBranch, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -162,6 +174,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ Path: utils.String("g"), Ref: utils.String("feat/fake_repo"), + Type: api.RefTypeBranch, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -171,6 +184,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.GetEntriesInRef(ctx, "jimmy", "happygo", &api.GetEntriesInRefParams{ Path: utils.String("g"), Ref: utils.String("main"), + Type: api.RefTypeBranch, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) @@ -180,6 +194,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ Path: utils.String("a/b/c/d"), Ref: utils.String(branchName), + Type: api.RefTypeBranch, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -189,6 +204,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ Path: utils.String("g"), Ref: utils.String(branchName), + Type: api.RefTypeBranch, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) @@ -204,6 +220,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ Path: utils.String("/"), Ref: utils.String(branchName), + Type: api.RefTypeBranch, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) diff --git a/integrationtest/objects_test.go b/integrationtest/objects_test.go index 763023bd..a0e1bb38 100644 --- a/integrationtest/objects_test.go +++ b/integrationtest/objects_test.go @@ -114,13 +114,16 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { }) //commit object to branch - c.Convey("commit wip", func() { - resp, err := client.CommitWip(ctx, userName, repoName, &api.CommitWipParams{ + commitWip(ctx, c, client, "commit wip", userName, repoName, branchName, "test commit msg") + + c.Convey("head object with no type", func() { + resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ RefName: branchName, - Msg: "test commit msg", + Path: "a.bin", + Type: api.RefTypeTest, }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) }) c.Convey("head object", func(c convey.C) { @@ -130,6 +133,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ RefName: branchName, Path: "a.bin", + Type: api.RefTypeBranch, }) client.RequestEditors = re convey.So(err, convey.ShouldBeNil) @@ -140,6 +144,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.HeadObject(ctx, "mock user", repoName, &api.HeadObjectParams{ RefName: branchName, Path: "a.bin", + Type: api.RefTypeBranch, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -149,6 +154,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.HeadObject(ctx, userName, "fakerepo", &api.HeadObjectParams{ RefName: branchName, Path: "a.bin", + Type: api.RefTypeBranch, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -158,6 +164,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ RefName: "mockref", Path: "a.bin", + Type: api.RefTypeBranch, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -167,6 +174,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.HeadObject(ctx, "jimmy", "happygo", &api.HeadObjectParams{ RefName: branchName, Path: "a.bin", + Type: api.RefTypeBranch, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) @@ -175,6 +183,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("empty path", func() { resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ RefName: branchName, + Type: api.RefTypeBranch, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) @@ -184,6 +193,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ RefName: branchName, Path: "c/d.txt", + Type: api.RefTypeBranch, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) @@ -193,6 +203,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ RefName: branchName, Path: "a.bin", + Type: api.RefTypeBranch, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) @@ -201,6 +212,16 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { }) }) + c.Convey("get object with no type", func() { + resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ + RefName: branchName, + Path: "a.bin", + Type: api.RefTypeTest, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + c.Convey("get object", func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors @@ -208,6 +229,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ RefName: branchName, Path: "a.bin", + Type: api.RefTypeBranch, }) client.RequestEditors = re convey.So(err, convey.ShouldBeNil) @@ -218,6 +240,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.GetObject(ctx, "mock user", repoName, &api.GetObjectParams{ RefName: branchName, Path: "a.bin", + Type: api.RefTypeBranch, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -227,6 +250,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.GetObject(ctx, userName, "fakerepo", &api.GetObjectParams{ RefName: branchName, Path: "a.bin", + Type: api.RefTypeBranch, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -236,6 +260,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ RefName: "mockref", Path: "a.bin", + Type: api.RefTypeBranch, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -245,6 +270,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.GetObject(ctx, "jimmy", "happygo", &api.GetObjectParams{ RefName: branchName, Path: "a.bin", + Type: api.RefTypeBranch, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) @@ -253,6 +279,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("empty path", func() { resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ RefName: branchName, + Type: api.RefTypeBranch, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) @@ -262,6 +289,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ RefName: branchName, Path: "c/d.txt", + Type: api.RefTypeBranch, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) @@ -271,6 +299,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ RefName: branchName, Path: "a.bin", + Type: api.RefTypeBranch, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) diff --git a/integrationtest/wip_object_test.go b/integrationtest/wip_object_test.go index c182362b..5944da46 100644 --- a/integrationtest/wip_object_test.go +++ b/integrationtest/wip_object_test.go @@ -33,7 +33,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ RefName: branchName, Path: "m.dat", - IsWip: utils.Bool(true), + Type: api.RefTypeWip, }) client.RequestEditors = re convey.So(err, convey.ShouldBeNil) @@ -44,7 +44,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.HeadObject(ctx, "mock user", repoName, &api.HeadObjectParams{ RefName: branchName, Path: "m.dat", - IsWip: utils.Bool(true), + Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -54,7 +54,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.HeadObject(ctx, userName, "fakerepo", &api.HeadObjectParams{ RefName: branchName, Path: "m.dat", - IsWip: utils.Bool(true), + Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -64,7 +64,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ RefName: "mockref", Path: "m.dat", - IsWip: utils.Bool(true), + Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -74,7 +74,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.HeadObject(ctx, "jimmy", "happygo", &api.HeadObjectParams{ RefName: branchName, Path: "m.dat", - IsWip: utils.Bool(true), + Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) @@ -82,8 +82,8 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("empty path", func() { resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ - Path: "", - IsWip: utils.Bool(true), + Path: "", + Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) @@ -93,7 +93,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ RefName: branchName, Path: "c/d.txt", - IsWip: utils.Bool(true), + Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) @@ -103,7 +103,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ RefName: branchName, Path: "m.dat", - IsWip: utils.Bool(true), + Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) @@ -117,7 +117,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ RefName: branchName, Path: "m.dat", - IsWip: utils.Bool(true), + Type: api.RefTypeWip, }) client.RequestEditors = re convey.So(err, convey.ShouldBeNil) @@ -128,7 +128,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.GetObject(ctx, "mock user", repoName, &api.GetObjectParams{ RefName: branchName, Path: "m.dat", - IsWip: utils.Bool(true), + Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -138,7 +138,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.GetObject(ctx, userName, "fakerepo", &api.GetObjectParams{ RefName: branchName, Path: "m.dat", - IsWip: utils.Bool(true), + Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -148,7 +148,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ RefName: "mockref", Path: "m.dat", - IsWip: utils.Bool(true), + Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) @@ -158,7 +158,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.GetObject(ctx, "jimmy", "happygo", &api.GetObjectParams{ RefName: branchName, Path: "m.dat", - IsWip: utils.Bool(true), + Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) @@ -167,7 +167,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("empty path", func() { resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ RefName: branchName, - IsWip: utils.Bool(true), + Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) @@ -177,7 +177,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ RefName: branchName, Path: "c/d.txt", - IsWip: utils.Bool(true), + Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) @@ -187,7 +187,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ RefName: branchName, Path: "m.dat", - IsWip: utils.Bool(true), + Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) @@ -266,7 +266,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ RefName: branchName, Path: "g/m.dat", - IsWip: utils.Bool(true), + Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) @@ -284,7 +284,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err = client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ RefName: branchName, Path: "g/m.dat", - IsWip: utils.Bool(true), + Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) From 0c019fde7b6962658baf29a02872d2264cce7c0b Mon Sep 17 00:00:00 2001 From: brown Date: Thu, 21 Dec 2023 19:07:18 +0800 Subject: [PATCH 102/210] chore: add pagination after type --- api/jiaozifs.gen.go | 147 ++++++++++++++++++++++---------------------- api/swagger.yml | 16 +++-- 2 files changed, 87 insertions(+), 76 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 23acb632..1bf353c7 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -264,15 +264,18 @@ type Wip struct { UpdatedAt *time.Time `json:"UpdatedAt,omitempty"` } -// PaginationAfter defines model for PaginationAfter. -type PaginationAfter = string - // PaginationAmount defines model for PaginationAmount. type PaginationAmount = int +// PaginationBranchAfter defines model for PaginationBranchAfter. +type PaginationBranchAfter = string + // PaginationPrefix defines model for PaginationPrefix. type PaginationPrefix = string +// PaginationRepoAfter defines model for PaginationRepoAfter. +type PaginationRepoAfter = time.Time + // LoginJSONBody defines parameters for Login. type LoginJSONBody struct { Password string `json:"password"` @@ -349,7 +352,7 @@ type ListBranchesParams struct { Prefix *PaginationPrefix `form:"prefix,omitempty" json:"prefix,omitempty"` // After return items after this value - After *PaginationAfter `form:"after,omitempty" json:"after,omitempty"` + After *PaginationBranchAfter `form:"after,omitempty" json:"after,omitempty"` // Amount how many items to return Amount *PaginationAmount `form:"amount,omitempty" json:"amount,omitempty"` @@ -385,7 +388,7 @@ type ListRepositoryOfAuthenticatedUserParams struct { Prefix *PaginationPrefix `form:"prefix,omitempty" json:"prefix,omitempty"` // After return items after this value - After *PaginationAfter `form:"after,omitempty" json:"after,omitempty"` + After *PaginationRepoAfter `form:"after,omitempty" json:"after,omitempty"` // Amount how many items to return Amount *PaginationAmount `form:"amount,omitempty" json:"amount,omitempty"` @@ -397,7 +400,7 @@ type ListRepositoryParams struct { Prefix *PaginationPrefix `form:"prefix,omitempty" json:"prefix,omitempty"` // After return items after this value - After *PaginationAfter `form:"after,omitempty" json:"after,omitempty"` + After *PaginationRepoAfter `form:"after,omitempty" json:"after,omitempty"` // Amount how many items to return Amount *PaginationAmount `form:"amount,omitempty" json:"amount,omitempty"` @@ -6526,72 +6529,72 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xcW3PbuJL+KyjueUh2qYsvk9rjqalTjuNMvOtkXLYzeYi9KohsSkhIgAOAljUp//ct", - "ALwTpChZvs2ZF5dFAuhGd+NDXwD+cDwWxYwClcI5+OHEmOMIJHD96wzPCMWSMHoYSODqkQ/C4yRWz5wD", - "h4NMOEVEQiQQVm2QnBOBbnCYgOM6RDX6IwG+dFyH4gicA0c3c1xHeHOIsBpTLmP1QkhO6My5u3PLhCOW", - "UNmkPGcLFGG6TGlLhgwvbUTNMGWqPgQ4CaVzsDMeu06Eb0mURPqX+kmo+TnYcTP+CJUwA15j8IxDQG5X", - "iCbWjcBHCyLnq0VkmnfK6C57qRV1mMg5UEk8zdIl+w5Ua5OzGLgkoBvJ7HGVUYz+58sl0i+RnGOJPJaE", - "PpoCSgT4SrK4GB0Qhz8SEFI4bp0n11CYwG1MODaj14l9puQWHcfMmyNCkQCPUV8NFTAeYWmE/Gbfscpc", - "USYcfOfgazqX67wdm34DTyoe3nJMvXlz9kcsioj8gMXcIk7XOeKAJfiH2tRybnwsYSBJBLbZ6i6Mn7yr", - "dEkS4ttavyvLwcJAz2E+aQux9D+HmAkiGV/2HOlz7K8345oKTt45NapuWcgpq2UxlaVcpt+uRt0+lVhV", - "nbRNDoIl3AM7rJTZp4a7tHk7C6dEyCb5OF//6tc/OATOgfMfowJMR+nqHBVI4WgORBIaqNXIsKp3as13", - "OXuYc7xsTKbETkHDNqejOaYzaM7n0MvmAlTB3tcdd9fdu26uQ9d5iwW0LqMzLO0vLllLn9pM9ABuxo91", - "CtrGLFNI5JzxVQK9IDOKZcKhGCrd2fr3Wh8qWuX1EfgMLvGs5aUQeAYtguZA9UqDqjU1QbliOBsAxSWH", - "doXfF0VSrLhUjRpwkqq0rKiSyAoBlXisSWYdyDEtCxaaJrYKw1vAuTZl3crGwCmbEXrEaEBmTdrnbw+P", - "mvupeooWJAwRhwgTioDiaQg+YhT9+vkEkQBdOXArgVMcXjlDhC7VFs9ouEQLxr+LK6pdEkxR1kpv90gA", - "vyEeDK8UoqSY4AgSxSEJCCg7ydqXplJIIsBhOMXe90mo5jQJ8RTCJvf6sfIw4hB7oHiu9Ut4OHRWD59w", - "y+DGucB8iT6fnyoiLAiAK6eGa38xEYACxpEewkrFDO4x9p3ARO0XoknFvEX6be4wCck4KLdK+Xe9F6Yh", - "F2ASgj+JirVfJZi+UGR8IuIQL9PJcIEWc4ZUf/VEj/YzwihIwhAJoBKoB8bDIwJxoD5w8K8ooejD5cdT", - "hKmPIrxEHqNSWRJGIaHftf+HClnqYVEEcs58bRstUrOqJOYkKimklwZYIu2DNQeZETpDLJHDlahT8GjV", - "coWwbaX+pv+7kDiNmior1ZuD912oFWNRupIuUDkxL+pzMuOiCHyCkTSY2BgiAol9LPGqTcsM9lkA/5j1", - "UL01LG/PMXeduG3PVy8mEfOhus0QKvd2rSMJ8idMpktp5LhuTJDLPWUpZSAVo5l3uzIrcjr44WDfJ0o2", - "ODyrRlEty7gY76ziG1ZtY47FJGLcooBPcCtRrFY2EQjfYBIqIC9mPWUsBKydyAjfTmLgk9gKEB9VJItD", - "RJNoChyxAAGVnIBAMXBNwSnFt2ObHijcygkLAgGWyFtHlznUcVBj3yhgAUSzOdjMtuT61maeM6pjYoEC", - "llBfmaEaM+vWzXPNFHIx14RVcFGdpM0suhyBJ48WPwD2HySM3EpUmEZ+mslNA8BC/E8bgZXMYGtR2AXI", - "JFa7hyUS81gUTWIOgZhERAgl48aKkTwB5dqp9aHa6/ySQJgDSvsMrcCRbXWZh9k177IzqqA54zbzBQkl", - "kuCQ/KmdQcrkpPzk2mYnTTnkYVVDDMcRJmHFBkE/WceWv8xNvmsDM04t+DilqUeyaVLFHcdU2jCiNWQ6", - "Ee8IL70pKag9gGhQNqtn82jFOqYAfkIDZrHK9QHPS7gKxJSOT+jGHU/Oqq5AfLNv6wP9zSXEYgOmil49", - "OUq0ftYhoXx42iuCzFtmE79uUeY5zIiQbUpdQ2gxFmLBuN5zIkJPgc6U0/ff251GiY5tRr8DF4TRc42s", - "zengmExuTBNLOj6hSu4oa2BVsQQhy0M0mrQOH3M24zhqH7429aJdmWvbpL+QuDnVt1hAkQd7/GT2kVmi", - "Cv2eRzI730zLQYM1xtjEwakpRW2H4CWcyOWF2i2NTqZYEG+CExMM6W1Uo7t6XIw6lzI2caCON7PmpMgl", - "FGUgxTWnONStJgJE1bRwTP4XtFfybSEneXVnCpgDf5/NzGQhCnb02zo/akokxYiqYX8jmP1JAoE+XF6e", - "ocOzExUcEw+ogCIN7xzG2JsD2h2OHdfR0boeWByMRovFYoj16yHjs1HaV4xOT46OP10cD3aH4+FcRqH2", - "rogMoUzU0MtXnbMzHA/HqiWLgeKYOAfOnn5kYj2th5GS1ki7OnrhMOM9quWjfbMT3zkwqTbHrEkQ8i3z", - "l8b50tG5QZM4TOtpo2/CrPmiHFf3RQt03A4eduDgnekmYqbkqEbdHY/XYr7L7bNVEjXFWnIt8TwQIkhC", - "k71xXGcO2E/rxhcgB0fGmCuE4RZHcdhq2r/gqefDzu7eT29+RmdYzn8Z/Yw+SBn/RsOlZWEqtvbHO7Zk", - "BtaZY+WKot9xSHw9m2POmcaA/d2xxalmzFSU8wqnnnVaJK63PkkngC6A3wBH6dglaHAOvl67jkiiCCvv", - "zImBK7hBOJeYxDOh9K5B4Fr1zW2XJbLTeNV7uxV06Un1ep4ys0vJzNIiJrMWRj/YggK/G/3g+X5xZ8iG", - "YLaDquDe6ecm39MU336TY0MHmfF8VEgzXPYSpGm012z0nvEp8X2gpoWF9Ccm37OE+uvIviJJwzQyUxii", - "jyYwTH8LUzSgTKbnJhBGGUUESi/DkuTTPs71nevMwGKRv4LMpVo+QvK1zjQRX0iMCPXNYYJyAingLNI1", - "CcUloUi7VCCEi1KDQgEORduRCT2w7cREHl3duXVm3i4lII7prMKILm1kOKWTkb+MBzvj3b2MsgG6gvS5", - "LqmWScdYKkt3Dpz/MwO8enV15f/nQP1x/4X+9fq/Xv/DgmfXa+E68yTIgZAccFSF2dyrmRKKuRU5Xbuh", - "Z6QqaH5kHg4yp99KqiNHe5zWN7sO/JxiIQcfmW+KS52NVfPd8ZvHkkyMuSQ4RA8poaz/eVacv7cpPYjU", - "98a7lgok+IQryehCUcxhIMiMgq+LPAHjOknFMnAoCe2UeXn6rptuT5htx28Fc0EOpjvj1ob66FI63s4b", - "22Q11IKPtKoUZKILLIkIiM7Wb4rVM5BNA7Oh7zzN+1bh9wNg/2/8fSj8bbEUYg7BvQig7ANpSAeA/464", - "9pfElw4/PAu+9CEP4MbfqyGSLqYiEqC6vdtQqQY5xBiZLsGma1Q76k453JU8gc5DwNZxCkd/3cGqIpjq", - "c3UjiWcKekydMWiBNg5BWg64B0EOIZbkBlaTSyfcn9a12xInfo5DVtoY+uU67uM8uU6UhJIofBmp1oOs", - "mN6WOCnxUDsIQcMlwkhFLCGggISgq9eJnhFazIk3R1EiJJqaszc+usoGu3KG5XMLHcz2SKzsbC2xUj4y", - "0u6AR6WTGvu27ccWmW8Uzm8WlSY8rKOdxSc84/r8iD4/8V6fZ1rTM2qAjOvcDm7yWQzg1gsTHwZTbctq", - "gei0gEaHDbMC51Vk6ZlY0cewTKDNK4XirSmvj7JscX8FKTN5qoedUfxKKWxlLZRr6s2loJzh5yLMGi8W", - "ST77va9je6hVkDdPh3cpu0Hmrpr31qt3zSVniqvPxkqa7DQMpRueRtP81ko3SqX3AexmtwW/5bpPVtQw", - "m+HehknRfZv3a25jaLe3O/l5ufXEczqbaSbgTH/mAXQnPx9fLdsD4+yKSROIp/nlkxeqU4Xe3Qp9ueht", - "avy54T0EctfuYPXC7Z0HpV47e69FkGo4w6H1doL+Fjv+Z0fTI0aDkHhSoC9EztEl5gopHs/QK5Kw23qv", - "Dcho0Ypyp0SkMKcPyNcWjk2RRZNR47qsWiS9+5jLx+t1Mfd9HwE49enUVvBEoX79YhFUsY+mhdpfKIiu", - "MH5PH6Vqt/1fQZrTVuKEVvzlzlw7h+BVkWd6jdKjHd0+wcP5AL1OO6eHyponna1RWia35q6bvkGEvvjw", - "abXtxJjD6McUC5gD9u9Wm9E7EgSrrEfE4JGAeEjNwkUk0HmX/Glatc+udig5Mya7U4pPbVvm7nEP2zLW", - "g3wlpm0Hdj/ZArs0EZ4nxqHFmSwxBhyoV8FE8/LFJMQtg2UmvOUFoo1IjLrWxbGxYwWvz2tluK3UDbS7", - "KC+P6hqmctYJ48v8qWn26sPx4bvX7ei/Hg9PWcV9FKgorlX0RotHzwD1y5A3vamyYWrFv0T80ItegEzi", - "rlVduuf0gH54iYrFOvKzxJpbZO4xrVVHbd0xiAcoocVdza7Tn3k9tcpPTf2MppGavs894un1jfajoNkF", - "j4fK3dbvkLQXyeq+r+pkGF0Zmb/FPkor32hQKlah53FgV+kClSdkP5OaqSxm3UF0EUD8FpROW4OvhP13", - "ZF2tD7VF1xpJn0v6v86MLczpyOE9eAWmQeYBMnn3v0vb0DGFxbNRcZpgW1XhMRig/nZti/k1ywdcQjkN", - "i2AviksB+uBhYCDONL+/9IxzdO/EPaHmDIfaCFh619lcOwv1Z0hm4A+I/hAB7wLkLCZZB5j/RuF+KFws", - "h1Ke85mgsP6MSRarZd7yo6SftG9cuinaBgO/53dAH0yF1RuzttPttXurXf5QGlhnXTD1keVWrc2bXZB4", - "w1M5Jhzd5DjOgsRPa48cInYDtVhc+8GFlBSTXXXl9ulvxTzU8BajsLD85Idweolx9Wrearpsoxi8USPo", - "VxfYWsXYalI7j29SKP2kxBPkbX6y3WPpdSjaeIE9bLEL9UaeToR3lpu+kPgobbW6yrRtC3KbFwa02f8V", - "Sgs2Q0wF/QwxLudtE6x7DinC9jVQfMX1xV0eeFTQ1nIyoN2JA2lpKso/iWpjLRKzZ3PurGWnSOeROXTa", - "y0xZMF9Pp7B4EufuPvuGmZNlfUvWPLTTYwcJ00+itcayW3AcewHvF6OI9VH3GcSKC11DK4LEmDN9B0OZ", - "XC2r8IJAtxrA/Sh/G+brtSJW/k6NeVL5Fs3Xa7XqjTHbcKZUvzD2Tv2YmY/tFB9+ORiNQubhcM6EPNjb", - "/+fO3gjHZHSz4zTRdOWAedfru/8PAAD//+/lMOGvYgAA", + "H4sIAAAAAAAC/+xc2XPbOJr/V1DceejsSpZ8dGrHXV1TjuN0vOukXbHTeYi9Koj8KCEhAQ4AWlan/L9v", + "4eANUpQsXzP94rJIHN+FH74D4A/PZ3HCKFApvMMfXoI5jkEC17/O8YxQLAmjRzFLqVTPAhA+J4l66B16", + "c7ZAMaZLRCTEAkmGOMiUU2/gEfX+nynwpTfwKI7BO/SwGWbgCX8OMTbjhTiNpHe4Ox4PvBjfkjiN9S/1", + "k1Dzc7g78OQyUWMQKmEG3Lu7G5QIfMMx9edHoQTepNLQZGnEqg2ScyLQDY5SaCNVD1Wm1M4vJCd0Vpv+", + "nENIblfMnOhGEKAFkfPVFJjmvUn4BAl7UP5DxmMsvUMvwBKGksSqa52iu6yHNqCjVM6BSuJrCi/Zd6Da", + "yjhLgEsCupHMHleJxuh/vlwi/RLJOZbIZ2kUoCmgVECgTA0XowPi8M8UhBRNmgZmhgncJoRjM3p9ss+U", + "3KKThPlzRCgS4DMaqKFyngmVrw88pxGqmQmHwDv8anm5ztux6TfwpaLBGGiT+2MWx0S+x2LuUPDAO+aA", + "JQRHsq8GbBfGT99WuqQpCVyt35bl4CCg5zAftdU4+iuzFEQyvuw50uckWI/jmgpO33q1WQdlIVtSy2Iq", + "S7k8f7sadXsrsao6aZscBEu5D+5FXCafGups83YSzoiQzemTHA7Ur79xCL1D7z9GBciP7OocFcDhaQpE", + "GpktQKPEqt7Wmu9y8jDneNlgpkROMYeLp+M5pjNo8nPkZ7wAVfvA193B3mD/urkOB94bLKB1GZ1j6X5x", + "yVr61DjRAwwyepwsaBtzsJDKOeOrBHpBZhTLlEMxlIXy/r3Wh4pWeX0APoNLPGt5KQSeQYugOVC90qBq", + "TU1QrhjOBkBxyaFd4fdFEYsVl6pRA06sSsuKKomsEFCJxppk1oEc07IgoWliqzC8BZxrLOtWLgLO2IzQ", + "Y0ZDMmvO/enN0XFzP1VP0YJEEeIQY0IRUDyNIECMot8+nyISoisPbiVwiqMrbwehS7XFMxot0YLx7+KK", + "aicJU5S10ts9EsBviA87VwpRLCZ4gsRJREICyk6y9iVWCkmEOIqm2P8+iRRPkwhPIWpSrx8rDyOJsA+K", + "5lq/lEc73urhU+4Y3DgXmC/R509nahIWhsCVU8O1A50KQCHjSA/hnMUM7jP2ncBE7ReiOYt5i/Tb3GES", + "knFQbpXy+XovTDNdiEkEwSQu1n51QvtCTRMQkUR4aZnhAi3mDKn+6oke7ReEUZhGERJAJVAfjIdHBOJA", + "A+AQXFFC0fvLD2cI0wDFeIl8RqWyJIwiQr9r/w8VstTDohjknAXaNlqk5lRJwklcUkgvDbBUugdrDjIj", + "dIZYKndWok5Bo1PLlYldK/V3/d+FxDaaq6xUfw7+d6FWjEPpSrpA5cS8qPNkxkUxBAQjaTCxMUQMEgdY", + "4lWblhnsswD+IeuhemtY3p5jPvCStj1fvZjELIDqNkOo3N9zjiTInzCZLqWR47oxQS53S5IlwIrR8N2u", + "zIqcDn94OAiIkg2OzqtRVMsyLsY7r/iGVduYYzGJGXco4CPcSpSolU0EwjeYRArIC66njEWAtRMZ49tJ", + "AnySOAHigwrtcYRoGk+BIxYioJITECgBrmfwSgH/2KUHCrdywsJQgCMVoaPLHOo4qLFvFLAAohkPLrMt", + "ub41znNCdZwsUMhSGigzVGNm3bpprplCLuaasAoqqky6zKLLEXjyaPE94OBBwsitRIU28tNEbhoAFuJ/", + "2gisZAZbi8IuQKaJ2j0ckZjP4niScAjFJCZCKBk3VozkKSjXTq0P1V5nvATCHJDts+MEjmyryzzMLr7L", + "zqiC5ozazBcklEiCI/KndgYpk5Pyk2uXnTTlkIdVDTGcxJhEFRsE/WQdW/4yN/muDczYWvCJnVOP5NKk", + "ijtOqHRhRGvIdCreEl56U1JQewDRmNmsns2jFeeYAvgpDZnDKtcHPD/lKhBTOj6lG3c8Pa+6AsnNgasP", + "9DeXCIsNiCp69aQo1fpZZwrlw9NeEWTeMmP8ukWZn2BGhGxT6hpCS7AQC8b1nhMTegZ0ppy+/94uG6V5", + "XBz9AVzoBLzQhYw6OzghkxvTxJGaT6mSO8oaOFUsQcjyEI0mrcMnnM04jtuHr7FetCtT7WL6C0marL7B", + "Aoo82OMns4/NElXo9zyS2flmWg4anDHGJg5OTSlqOwQ/5UQuL9RuaXQyxYL4E5yaYEhvoxrd1eNi1LmU", + "iYkDdbyZNSdFLqEoDSmqOcWRbjURIKqmhRPyv6C9km8LOcmrO1PAHPi7jDOThSjI0W/r9CiWiMWIqmF/", + "I5j9SUKB3l9enqOj81MVHBMfqIAiDe8dJdifA9rbGXsDT0fremBxOBotFosdrF/vMD4b2b5idHZ6fPLx", + "4mS4tzPemcs40t4VkRGUJzXz5avO290Z74xVS5YAxQnxDr19/cjEeloPIyWtkXZ19MJhxntUy0f7ZqeB", + "d2hSbZ5ZkyDkGxYsjfOlo3ODJklk62mjb8Ks+aJGV/dFC3TcDh524OCd6SYSpuSoRt0bj9civsvtc1US", + "9Yy15Frq+yBEmEYme+MNvDngwNazL0AOj40xVyaGWxwnUatp/4qnfgC7e/s/v/4FnWM5/3X0C3ovZfI7", + "jZauGujdwDsY77qSGVhnjpUriv7AEQk0NyecM40BB3tjh1PNmCmx5xVOzbWtmtdbn1oG0AXwG+DIjl2C", + "Bu/w6/XAE2kcY+WdeQlwBTcI5xKTeCaU3jUIXKu+ue2yVHYar3rvtoIuPalez1NmbikZLh1iMmth9IMt", + "KPC70Q+e7xd3ZtoIzHZQFdxb/dzke5riO2hSbOZBZrwAFdKMlr0EaRrtNxu9Y3xKggCoaeGY+iOT71hK", + "g3VkX5GkIRoZFnbQBxMY2t/CFA0ok/YgCcIomxGB0stOSfK2j3d9N/Bm4LDI30DmUi0fbflaJ5qILyRB", + "hAbmMEE5gRRyFuuahKKSUKRdKhBigKxBoRBHou0YhR7YdYYjj67uBnVi3iwlII7prEKILm1kOKWTkb+O", + "h7vjvf1sZgN0xdSfdEm1PHWCpbJ079D7PzPATz9dXQX/OVR/Bv9A/3j1X6/+5sCz67VwnfkS5FBIDjiu", + "wmzu1UwJxXzpPj3iNPRsqgqaH5uHw8zpd07VkaM9sfXNruM1Z1jI4QcWmOJSZ2PVfG/8+rEkk2AuCY7Q", + "Q0oo6/8pK87f25QeROr74z1HBRICwpVkdKEo4TAUZEYh0EWekHGdpGIZOJSEdsb8PH3XPW9PmG3HbwVz", + "YQ6mu+PWhvrokh1v97WLWQ21ECCtKgWZ6AJLIkKis/WbYvUMZNPAXOg7t3nfKvy+Bxz8hb8Phb8tlkLM", + "IbgXAZR9IA3pAPDfEdf+JfGlww/Pgi99yAO48fdqiKSLqYiEqG7vLlSqQQ4xRqZLsHaNakfdK4e7kqfQ", + "eeTWOU7h6K87WFUEU32ubiTxTEGPqTOGLdDGIbTlgHtMyCHCktzA6uksw/3nuh60xImfk4iVNoZ+uY77", + "OE8DL04jSRS+jFTrYVZMb0uclGioHYSg0RJhpCKWCFBIItDV61RzhBZz4s9RnAqJpubsTYCussGuvJ3y", + "uYUOYnskVna3llgpHxlpd8Dj0kmNA9f244rMNwrnN4tKUx7V0c7hE55zfX5En594p88zrekZNUBm4N0O", + "b3IuhnDrR2kAw6m2ZbVAdFpAo8OGWYFPVWTpmVjRx7BMoM0rheKtKa+PslxxfwUpM3mqh51R/EopbGUt", + "lGvqzaWgnOHnIswaLQ5JPvu9r2N7qFWQN0+Hdym7Mc1dNe+tV++aS84UV5+NlTTJaRhKNzyNpvmtlW6U", + "svcB3Ga3Bb/luk9W1BCb4d6GSdEDl/drbmNot7c7+Xm59cSz5WaaCTjTn3kA3cnPx1fL9sA4u2LSBOJp", + "fvnkhepUoXe3Ql8uepsaf254D4HctTtYvXB790Fnr5291yKwGs5waL2doL/Fjv/e0fSY0TAivhToC5Fz", + "dIm5QorHM/SKJNy23msDMlp0otwZERbm9AH52sJxKbJoMmpc4FWLpHef8p3jtTra29SPAJ/6jGorhKJI", + "v36xOKrIR9NC+S8USlcsAV8fqGpfAb+BNGeuxCmteM2dGXcO4U9FtukVsgc8uj2Dh/MEep15tkfLmued", + "nbFaJrfm3mvfIEJffBC12nYSzGH0Y4oFzAEHd6vN6C0Jw1XWIxLwSUh8pLgYIBLq7Ev+1NbuswseSs6M", + "ye7E4lPblrmB3MO2jPWgQIlp2+Hdz67wzqbD8/Q4tLiUJcKAA/UrmGhevpi0uGOwzIS3vEC0EYlR17o4", + "MXas4PV5rYxB6+wG2gcoL5LqSqZy2Qnjy/ypafbT+5Ojt6/a0X89Gp6ylvsoUFFcruiNFo+eB+qXJ296", + "U2XD1Ip/ifihF70AmSZdq7p02+kB/fDSLA7ryE8Ua2qRuc20VjW1dccgPqCUFjc2u86A5lXVKj019TNq", + "4zV9q3vE7SWO9gOh2TWPh8rg1m+StJfK6r6v6mQIXRmfv8EBsvVvNCyVrNDzOLardIHKDLlPpmYqS1h3", + "KF0EEL+HpTPXEChhP3J8XXzR6tlF17VboI6VrdH0uRQC6sS4Qp2ObN6D12Ia0zxATu/+t2obOqaweDYq", + "tqm2VbUegwPqb9fWmF+4fMAllM/hEOxFcT1AH0EMDcyZ5veXnnGQ7p3CJ9Sc5lCbAbO3ns0FtEh/kGQG", + "wZDoTxLwLlDO4pJ1wPkvJO6PxMWSKOU7nwkS64+aZDFb5jU/ShpK+8ile6NtUPBHfiP0wVRYvT/rOute", + "u8Xa5RfZADvrgmmAHHdsXV7tgiQbntExYekmh3MWJHlae+QQsxuoxeTaHy6kpIjsqjK3s78V81DDO4zC", + "QfKTH8npJcbVq3mrabONYvFGraBffWBr9WOnSe0+vkkh+4GJJ8jf/Oy61dLriLTxBHvYYhfqjXydEO8s", + "O30hybFttbratG0LGjSvD2iz/1coMbgM0Qr6GWJcTtsmWPccUoXta6D4puuLu0rwqKCt5WRAuxMHbIkq", + "zj+Q6iItFrNncwqtZaewfGQOnfYyLQnm6+4UFk/i3N1n3zA8Oda3ZM0jPD12kMh+IK01nt2C49gLeL8Y", + "RayPus8gVlzoWloRJCac6RsZyuRqmYUXBLrVAO5H+UsxX6/VZOWv1pgnlS/TfL1Wq94YswtnSnUMY+80", + "SJj59E7xGZjD0ShiPo7mTMjD/YO/7+6PcEJGN7teE01XDph3vb77/wAAAP//jpw2I1VjAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 4b472738..258c7457 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -26,12 +26,20 @@ components: schema: type: string - PaginationAfter: + PaginationBranchAfter: in: query name: after description: return items after this value schema: type: string + + PaginationRepoAfter: + in: query + name: after + description: return items after this value + schema: + type: string + format: date-time PaginationAmount: in: query @@ -1213,7 +1221,7 @@ paths: summary: list repository in specific owner parameters: - $ref: "#/components/parameters/PaginationPrefix" - - $ref: "#/components/parameters/PaginationAfter" + - $ref: "#/components/parameters/PaginationRepoAfter" - $ref: "#/components/parameters/PaginationAmount" responses: 200: @@ -1237,7 +1245,7 @@ paths: summary: list repository parameters: - $ref: "#/components/parameters/PaginationPrefix" - - $ref: "#/components/parameters/PaginationAfter" + - $ref: "#/components/parameters/PaginationRepoAfter" - $ref: "#/components/parameters/PaginationAmount" responses: 200: @@ -1298,7 +1306,7 @@ paths: summary: list branches parameters: - $ref: "#/components/parameters/PaginationPrefix" - - $ref: "#/components/parameters/PaginationAfter" + - $ref: "#/components/parameters/PaginationBranchAfter" - $ref: "#/components/parameters/PaginationAmount" responses: 200: From e4e393ea6633de401eba3c68e337559d7531a65e Mon Sep 17 00:00:00 2001 From: brown Date: Thu, 21 Dec 2023 19:12:11 +0800 Subject: [PATCH 103/210] feat: add pagination util --- controller/branch_ctl.go | 63 +++++++++----------------- controller/repository_ctl.go | 86 ++++++++++++------------------------ utils/pagination.go | 37 ++++++++++++++++ 3 files changed, 86 insertions(+), 100 deletions(-) create mode 100644 utils/pagination.go diff --git a/controller/branch_ctl.go b/controller/branch_ctl.go index feeb8893..85029513 100644 --- a/controller/branch_ctl.go +++ b/controller/branch_ctl.go @@ -5,11 +5,11 @@ import ( "errors" "fmt" "net/http" - "reflect" "regexp" "strings" "time" + "github.com/jiaozifs/jiaozifs/utils" "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/jiaozifs/jiaozifs/auth" @@ -22,28 +22,6 @@ import ( var MaxBranchNameLength = 40 var branchNameRegex = regexp.MustCompile("^[a-zA-Z0-9_]*$") -func paginationForBranches(hasMore bool, results interface{}, fieldName string) api.Pagination { - pagination := api.Pagination{ - HasMore: hasMore, - MaxPerPage: DefaultMaxPerPage, - } - if results == nil { - return pagination - } - if reflect.TypeOf(results).Kind() != reflect.Slice { - panic("results is not a slice") - } - s := reflect.ValueOf(results) - pagination.Results = s.Len() - if !hasMore || pagination.Results == 0 { - return pagination - } - v := s.Index(pagination.Results - 1) - token := v.FieldByName(fieldName) - pagination.NextOffset = token.String() - return pagination -} - func CheckBranchName(name string) error { for _, blackName := range RepoNameBlackList { if name == blackName { @@ -85,33 +63,25 @@ func (bct BranchController) ListBranches(ctx context.Context, w *api.JiaozifsRes return } - repository, err := bct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) - if err != nil { - w.Error(err) + if operator.Name != owner.Name { + w.Forbidden() return } - if operator.Name != owner.Name { - w.Forbidden() + repository, err := bct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) + if err != nil { + w.Error(err) return } listBranchParams := models.NewListBranchParams() - if params.Prefix != nil && len(*params.Prefix) > 0 { - listBranchParams.SetName(*params.Prefix, models.PrefixMatch) - } - if params.After != nil { - listBranchParams.SetAfter(*params.After) - } - if params.Amount != nil { - i := *params.Amount - if i > DefaultMaxPerPage || i <= 0 { - listBranchParams.SetAmount(DefaultMaxPerPage) - } else { - listBranchParams.SetAmount(i) - } + listBranchParams.SetName(params.Prefix, models.PrefixMatch) + listBranchParams.SetAfter(params.After) + pageAmount := utils.IntValue(params.Amount) + if pageAmount > utils.DefaultMaxPerPage || pageAmount <= 0 { + listBranchParams.SetAmount(utils.DefaultMaxPerPage) } else { - listBranchParams.SetAmount(DefaultMaxPerPage) + listBranchParams.SetAmount(pageAmount) } branches, hasMore, err := bct.Repo.BranchRepo().List(ctx, listBranchParams.SetRepositoryID(repository.ID)) @@ -133,8 +103,15 @@ func (bct BranchController) ListBranches(ctx context.Context, w *api.JiaozifsRes } results = append(results, r) } + pagMag := utils.PaginationFor(hasMore, results, "Name") + pagination := api.Pagination{ + HasMore: pagMag.HasMore, + MaxPerPage: pagMag.MaxPerPage, + NextOffset: pagMag.NextOffset, + Results: pagMag.Results, + } w.JSON(api.BranchList{ - Pagination: paginationForBranches(hasMore, results, "Name"), + Pagination: pagination, Results: results, }) } diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index b984e91a..07b06019 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -5,7 +5,6 @@ import ( "errors" "io" "net/http" - "reflect" "regexp" "time" @@ -25,10 +24,7 @@ import ( "go.uber.org/fx" ) -const ( - DefaultBranchName = "main" - DefaultMaxPerPage int = 1000 -) +const DefaultBranchName = "main" var maxNameLength = 20 var alphanumeric = regexp.MustCompile("^[a-zA-Z0-9_]*$") @@ -36,28 +32,6 @@ var alphanumeric = regexp.MustCompile("^[a-zA-Z0-9_]*$") // RepoNameBlackList forbid repo name, reserve for routes var RepoNameBlackList = []string{"repository", "repositories", "wip", "wips", "object", "objects", "commit", "commits", "ref", "refs", "repo", "repos", "user", "users"} -func paginationForRepos(hasMore bool, results interface{}, fieldName string) api.Pagination { - pagination := api.Pagination{ - HasMore: hasMore, - MaxPerPage: DefaultMaxPerPage, - } - if results == nil { - return pagination - } - if reflect.TypeOf(results).Kind() != reflect.Slice { - panic("results is not a slice") - } - s := reflect.ValueOf(results) - pagination.Results = s.Len() - if !hasMore || pagination.Results == 0 { - return pagination - } - v := s.Index(pagination.Results - 1) - token := v.FieldByName(fieldName) - t := token.Interface().(time.Time) - pagination.NextOffset = t.String() - return pagination -} func CheckRepositoryName(name string) error { for _, blackName := range RepoNameBlackList { if name == blackName { @@ -88,21 +62,13 @@ func (repositoryCtl RepositoryController) ListRepositoryOfAuthenticatedUser(ctx } listParams := models.NewListRepoParams() - if params.Prefix != nil && len(*params.Prefix) > 0 { - listParams.SetName(*params.Prefix, models.PrefixMatch) - } - if params.After != nil { - listParams.SetAfter(*params.After) - } - if params.Amount != nil { - i := *params.Amount - if i > DefaultMaxPerPage || i <= 0 { - listParams.SetAmount(DefaultMaxPerPage) - } else { - listParams.SetAmount(i) - } + listParams.SetName(params.Prefix, models.PrefixMatch) + listParams.SetAfter(params.After) + pageAmount := utils.IntValue(params.Amount) + if pageAmount > utils.DefaultMaxPerPage || pageAmount <= 0 { + listParams.SetAmount(utils.DefaultMaxPerPage) } else { - listParams.SetAmount(DefaultMaxPerPage) + listParams.SetAmount(pageAmount) } repositories, hasMore, err := repositoryCtl.Repo.RepositoryRepo().List(ctx, listParams. @@ -124,8 +90,15 @@ func (repositoryCtl RepositoryController) ListRepositoryOfAuthenticatedUser(ctx } results = append(results, r) } + pagMag := utils.PaginationFor(hasMore, results, "UpdatedAt") + pagination := api.Pagination{ + HasMore: pagMag.HasMore, + MaxPerPage: pagMag.MaxPerPage, + NextOffset: pagMag.NextOffset, + Results: pagMag.Results, + } w.JSON(api.RepositoryList{ - Pagination: paginationForRepos(hasMore, results, "UpdatedAt"), + Pagination: pagination, Results: results, }) } @@ -148,21 +121,13 @@ func (repositoryCtl RepositoryController) ListRepository(ctx context.Context, w } listParams := models.NewListRepoParams().SetOwnerID(owner.ID) - if params.Prefix != nil && len(*params.Prefix) > 0 { - listParams.SetName(*params.Prefix, models.PrefixMatch) - } - if params.After != nil { - listParams.SetAfter(*params.After) - } - if params.Amount != nil { - i := *params.Amount - if i > DefaultMaxPerPage || i <= 0 { - listParams.SetAmount(DefaultMaxPerPage) - } else { - listParams.SetAmount(i) - } + listParams.SetName(params.Prefix, models.PrefixMatch) + listParams.SetAfter(params.After) + pageAmount := utils.IntValue(params.Amount) + if pageAmount > utils.DefaultMaxPerPage || pageAmount <= 0 { + listParams.SetAmount(utils.DefaultMaxPerPage) } else { - listParams.SetAmount(DefaultMaxPerPage) + listParams.SetAmount(pageAmount) } repositories, hasMore, err := repositoryCtl.Repo.RepositoryRepo().List(ctx, listParams) @@ -183,8 +148,15 @@ func (repositoryCtl RepositoryController) ListRepository(ctx context.Context, w } results = append(results, r) } + pagMag := utils.PaginationFor(hasMore, results, "UpdatedAt") + pagination := api.Pagination{ + HasMore: pagMag.HasMore, + MaxPerPage: pagMag.MaxPerPage, + NextOffset: pagMag.NextOffset, + Results: pagMag.Results, + } w.JSON(api.RepositoryList{ - Pagination: paginationForRepos(hasMore, results, "UpdatedAt"), + Pagination: pagination, Results: results, }) } diff --git a/utils/pagination.go b/utils/pagination.go new file mode 100644 index 00000000..5196d274 --- /dev/null +++ b/utils/pagination.go @@ -0,0 +1,37 @@ +package utils + +import ( + "reflect" + "time" +) + +const DefaultMaxPerPage int = 1000 + +type PageManage struct { + HasMore bool + MaxPerPage int + NextOffset string + Results int +} + +func PaginationFor(hasMore bool, results interface{}, fieldName string) PageManage { + pagination := PageManage{ + HasMore: hasMore, + MaxPerPage: DefaultMaxPerPage, + } + + s := reflect.ValueOf(results) + pagination.Results = s.Len() + if !hasMore || pagination.Results == 0 { + return pagination + } + v := s.Index(pagination.Results - 1) + token := v.FieldByName(fieldName) + switch fieldName { + case "UpdatedAt": + pagination.NextOffset = token.Interface().(time.Time).String() + case "Name": + pagination.NextOffset = token.Interface().(string) + } + return pagination +} From 2b66a3c861ac73540eac01f5008aa968b7a5b882 Mon Sep 17 00:00:00 2001 From: brown Date: Thu, 21 Dec 2023 19:13:17 +0800 Subject: [PATCH 104/210] test: add list test of branch and repo --- integrationtest/branch_test.go | 42 ++++++++++++++++++++ integrationtest/repo_test.go | 72 ++++++++++++++++++++++++++++++++++ models/branch.go | 10 ++--- models/branch_test.go | 7 ++-- models/repository.go | 10 ++--- models/repository_test.go | 18 ++++++--- 6 files changed, 140 insertions(+), 19 deletions(-) diff --git a/integrationtest/branch_test.go b/integrationtest/branch_test.go index 417ae403..cddfc935 100644 --- a/integrationtest/branch_test.go +++ b/integrationtest/branch_test.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/jiaozifs/jiaozifs/controller" + "github.com/jiaozifs/jiaozifs/utils" "github.com/jiaozifs/jiaozifs/api" apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" @@ -18,6 +19,9 @@ func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { userName := "mike" repoName := "mlops" branchName := "feat/test" + amount := 1 + newAmount := 0 + prefix := "feat/" createUser(ctx, c, client, userName) loginAndSwitch(ctx, c, client, userName) @@ -170,6 +174,44 @@ func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(respResult.JSON200.Results, convey.ShouldHaveLength, 3) }) + c.Convey("success list branch by prefix", func() { + resp, err := client.ListBranches(ctx, userName, repoName, &api.ListBranchesParams{ + Prefix: &prefix, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + respResult, err := api.ParseListBranchesResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(respResult.JSON200.Results, convey.ShouldHaveLength, 2) + }) + + c.Convey("success list branch and next page exists", func() { + resp, err := client.ListBranches(ctx, userName, repoName, &api.ListBranchesParams{ + After: utils.String(branchName), + Amount: utils.Int(amount), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + respResult, err := api.ParseListBranchesResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(respResult.JSON200.Pagination.HasMore, convey.ShouldBeTrue) + convey.So(respResult.JSON200.Results, convey.ShouldHaveLength, 1) + }) + + c.Convey("success list branch, set amount 0", func() { + resp, err := client.ListBranches(ctx, userName, repoName, &api.ListBranchesParams{ + Amount: utils.Int(newAmount), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + respResult, err := api.ParseListBranchesResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(respResult.JSON200.Results, convey.ShouldHaveLength, 3) + }) + c.Convey("fail to list branchs from non exit user", func() { resp, err := client.ListBranches(ctx, "mock_owner", repoName, &api.ListBranchesParams{}) convey.So(err, convey.ShouldBeNil) diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index 23361960..21c82486 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -116,6 +116,42 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(len(listRepos.JSON200.Results), convey.ShouldEqual, 2) }) + c.Convey("success list repository of authenticatedUser and next page exists", func() { + resp, err := client.ListRepositoryOfAuthenticatedUser(ctx, &api.ListRepositoryOfAuthenticatedUserParams{}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + listRepos, err := api.ParseListRepositoryResponse(resp) + convey.So(err, convey.ShouldBeNil) + + convey.So(len(listRepos.JSON200.Results), convey.ShouldEqual, 2) + + newResp, err := client.ListRepositoryOfAuthenticatedUser(ctx, &api.ListRepositoryOfAuthenticatedUserParams{ + After: utils.Time(listRepos.JSON200.Results[0].UpdatedAt), + Amount: utils.Int(1), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(newResp.StatusCode, convey.ShouldEqual, http.StatusOK) + + newListRepos, err := api.ParseListRepositoryResponse(newResp) + convey.So(err, convey.ShouldBeNil) + convey.So(newListRepos.JSON200.Pagination.HasMore, convey.ShouldBeTrue) + convey.So(len(newListRepos.JSON200.Results), convey.ShouldEqual, 1) + }) + + c.Convey("success list repository of authenticatedUser, set page amount 0", func() { + resp, err := client.ListRepositoryOfAuthenticatedUser(ctx, &api.ListRepositoryOfAuthenticatedUserParams{ + Amount: utils.Int(0), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + listRepos, err := api.ParseListRepositoryResponse(resp) + convey.So(err, convey.ShouldBeNil) + + convey.So(len(listRepos.JSON200.Results), convey.ShouldEqual, 2) + }) + c.Convey("list repository", func() { resp, err := client.ListRepository(ctx, userName, &api.ListRepositoryParams{}) convey.So(err, convey.ShouldBeNil) @@ -138,6 +174,42 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(len(listRepos.JSON200.Results), convey.ShouldEqual, 2) }) + c.Convey("success list repository and next page exists", func() { + resp, err := client.ListRepository(ctx, userName, &api.ListRepositoryParams{}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + listRepos, err := api.ParseListRepositoryResponse(resp) + convey.So(err, convey.ShouldBeNil) + + convey.So(len(listRepos.JSON200.Results), convey.ShouldEqual, 2) + + newResp, err := client.ListRepository(ctx, userName, &api.ListRepositoryParams{ + After: utils.Time(listRepos.JSON200.Results[0].UpdatedAt), + Amount: utils.Int(1), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(newResp.StatusCode, convey.ShouldEqual, http.StatusOK) + + newListRepos, err := api.ParseListRepositoryResponse(newResp) + convey.So(err, convey.ShouldBeNil) + convey.So(newListRepos.JSON200.Pagination.HasMore, convey.ShouldBeTrue) + convey.So(len(newListRepos.JSON200.Results), convey.ShouldEqual, 1) + }) + + c.Convey("success list repository, set page amount 0", func() { + resp, err := client.ListRepository(ctx, userName, &api.ListRepositoryParams{ + Amount: utils.Int(0), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + listRepos, err := api.ParseListRepositoryResponse(resp) + convey.So(err, convey.ShouldBeNil) + + convey.So(len(listRepos.JSON200.Results), convey.ShouldEqual, 2) + }) + c.Convey("list repository by prefix but found nothing", func() { resp, err := client.ListRepository(ctx, userName, &api.ListRepositoryParams{Prefix: utils.String("bad")}) convey.So(err, convey.ShouldBeNil) diff --git a/models/branch.go b/models/branch.go index 271d5b91..bfff7a7c 100644 --- a/models/branch.go +++ b/models/branch.go @@ -108,14 +108,14 @@ func (gup *ListBranchParams) SetRepositoryID(repositoryID uuid.UUID) *ListBranch return gup } -func (gup *ListBranchParams) SetName(name string, match MatchMode) *ListBranchParams { - gup.Name = &name +func (gup *ListBranchParams) SetName(name *string, match MatchMode) *ListBranchParams { + gup.Name = name gup.NameMatch = match return gup } -func (gup *ListBranchParams) SetAfter(after string) *ListBranchParams { - gup.After = &after +func (gup *ListBranchParams) SetAfter(after *string) *ListBranchParams { + gup.After = after return gup } @@ -175,7 +175,7 @@ func (r BranchRepo) Get(ctx context.Context, params *GetBranchParams) (*Branches } func (r BranchRepo) List(ctx context.Context, params *ListBranchParams) ([]*Branches, bool, error) { - var branches []*Branches + branches := []*Branches{} query := r.db.NewSelect().Model(&branches) if uuid.Nil != params.RepositoryID { diff --git a/models/branch_test.go b/models/branch_test.go index 5956e136..33fe8f78 100644 --- a/models/branch_test.go +++ b/models/branch_test.go @@ -4,13 +4,13 @@ import ( "context" "testing" - "github.com/jiaozifs/jiaozifs/utils/hash" - "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" "github.com/google/uuid" "github.com/jiaozifs/jiaozifs/models" "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/jiaozifs/jiaozifs/utils" + "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/stretchr/testify/require" ) @@ -67,8 +67,7 @@ func TestRefRepoInsert(t *testing.T) { require.True(t, cmp.Equal(secModel, sRef, dbTimeCmpOpt)) - // amount - list, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetAmount(1)) + list, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(utils.String(secModel.Name[:3]), models.PrefixMatch).SetAfter(utils.String(branchModel.Name)).SetAmount(1)) require.NoError(t, err) require.Len(t, list, 1) require.True(t, hasMore) diff --git a/models/repository.go b/models/repository.go index ee8118ce..56a9bdcb 100644 --- a/models/repository.go +++ b/models/repository.go @@ -58,7 +58,7 @@ type ListRepoParams struct { OwnerID uuid.UUID Name *string NameMatch MatchMode - After *string + After *time.Time Amount int } @@ -75,8 +75,8 @@ func (lrp *ListRepoParams) SetOwnerID(ownerID uuid.UUID) *ListRepoParams { return lrp } -func (lrp *ListRepoParams) SetName(name string, match MatchMode) *ListRepoParams { - lrp.Name = &name +func (lrp *ListRepoParams) SetName(name *string, match MatchMode) *ListRepoParams { + lrp.Name = name lrp.NameMatch = match return lrp } @@ -86,8 +86,8 @@ func (lrp *ListRepoParams) SetCreatorID(creatorID uuid.UUID) *ListRepoParams { return lrp } -func (lrp *ListRepoParams) SetAfter(after string) *ListRepoParams { - lrp.After = &after +func (lrp *ListRepoParams) SetAfter(after *time.Time) *ListRepoParams { + lrp.After = after return lrp } diff --git a/models/repository_test.go b/models/repository_test.go index d6315a4c..dacad005 100644 --- a/models/repository_test.go +++ b/models/repository_test.go @@ -9,6 +9,7 @@ import ( "github.com/google/uuid" "github.com/jiaozifs/jiaozifs/models" "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/jiaozifs/jiaozifs/utils" "github.com/stretchr/testify/require" ) @@ -52,32 +53,32 @@ func TestRepositoryRepo_Insert(t *testing.T) { { //exact adabbeb - repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("adabbeb", models.PrefixMatch)) + repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName(utils.String("adabbeb"), models.PrefixMatch)) require.NoError(t, err) require.Len(t, repos, 1) } { //prefix a - repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("a", models.PrefixMatch)) + repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName(utils.String("a"), models.PrefixMatch)) require.NoError(t, err) require.Len(t, repos, 2) } { //subfix b - repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("b", models.SuffixMatch)) + repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName(utils.String("b"), models.SuffixMatch)) require.NoError(t, err) require.Len(t, repos, 2) } { //like ab - repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("ab", models.LikeMatch)) + repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName(utils.String("ab"), models.LikeMatch)) require.NoError(t, err) require.Len(t, repos, 2) } { //like ab - repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("adabbeb", models.LikeMatch)) + repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName(utils.String("adabbeb"), models.LikeMatch)) require.NoError(t, err) require.Len(t, repos, 1) } @@ -102,6 +103,13 @@ func TestRepositoryRepo_Insert(t *testing.T) { require.False(t, hasMore) require.Len(t, repos, 2) } + { + //after + repos, hasMore, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetAfter(utils.Time(secRepo.UpdatedAt)).SetAmount(1)) + require.NoError(t, err) + require.True(t, hasMore) + require.Len(t, repos, 1) + } //delete deleteParams := models.NewDeleteRepoParams(). SetID(secRepo.ID). From 9cea838e34ba748b641c0c5d482d2651d9650f02 Mon Sep 17 00:00:00 2001 From: brown Date: Thu, 21 Dec 2023 19:57:15 +0800 Subject: [PATCH 105/210] fix: fix branch list test --- api/jiaozifs.gen.go | 133 +++++++++++++++++++++--------------------- models/branch_test.go | 9 ++- 2 files changed, 75 insertions(+), 67 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 0aa66431..3a425d68 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -6549,72 +6549,73 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "mHe9ffj/AAAA///jGYBm82MAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/models/branch_test.go b/models/branch_test.go index 33fe8f78..f28d8630 100644 --- a/models/branch_test.go +++ b/models/branch_test.go @@ -23,6 +23,7 @@ func TestRefRepoInsert(t *testing.T) { branchModel := &models.Branches{} require.NoError(t, gofakeit.Struct(branchModel)) + branchModel.Name = "main/feat/aaa" newBrance, err := repo.Insert(ctx, branchModel) require.NoError(t, err) require.NotEqual(t, uuid.Nil, newBrance.ID) @@ -54,6 +55,7 @@ func TestRefRepoInsert(t *testing.T) { secModel := &models.Branches{} require.NoError(t, gofakeit.Struct(secModel)) secModel.RepositoryID = branch.RepositoryID + branchModel.Name = "main/feat/bbb" secRef, err := repo.Insert(ctx, secModel) require.NoError(t, err) require.NotEqual(t, uuid.Nil, secRef.ID) @@ -67,11 +69,16 @@ func TestRefRepoInsert(t *testing.T) { require.True(t, cmp.Equal(secModel, sRef, dbTimeCmpOpt)) - list, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(utils.String(secModel.Name[:3]), models.PrefixMatch).SetAfter(utils.String(branchModel.Name)).SetAmount(1)) + list, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(utils.String(secModel.Name[:3]), models.PrefixMatch).SetAmount(1)) require.NoError(t, err) require.Len(t, list, 1) require.True(t, hasMore) + newList, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetAfter(utils.String(branchModel.Name)).SetAmount(1)) + require.NoError(t, err) + require.Len(t, newList, 1) + require.True(t, hasMore) + affectedRows, err := repo.Delete(ctx, models.NewDeleteBranchParams().SetID(list[0].ID).SetRepositoryID(list[0].RepositoryID).SetName(list[0].Name)) require.NoError(t, err) require.Equal(t, int64(1), affectedRows) From 00629f0df6ca66fcf9fc43add5a2cc24169ed68b Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Thu, 21 Dec 2023 20:30:30 +0800 Subject: [PATCH 106/210] feat: support config storage for repository --- api/jiaozifs.gen.go | 117 ++++++++++++++++++----------------- api/swagger.yml | 95 ++++++++++++++++++++++++++-- block/factory/build.go | 2 + cmd/daemon.go | 1 + config/default.go | 5 +- controller/object_ctl.go | 50 ++++++++++++--- controller/repository_ctl.go | 56 ++++++++++++++--- models/repository.go | 14 ++++- versionmgr/worktree.go | 8 +-- versionmgr/worktree_test.go | 15 +++-- 10 files changed, 268 insertions(+), 95 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 6a9c7ff0..cb7d0221 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -116,8 +116,10 @@ type Commit struct { // CreateRepository defines model for CreateRepository. type CreateRepository struct { - Description *string `json:"Description,omitempty"` - Name string `json:"Name"` + // BlockStoreConfig block storage config url encoded json + BlockStoreConfig *string `json:"BlockStoreConfig,omitempty"` + Description *string `json:"Description,omitempty"` + Name string `json:"Name"` } // LoginConfig defines model for LoginConfig. @@ -6297,61 +6299,62 @@ var swaggerSpec = []string{ "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", + "AAD//xLQ6hqZXwAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index e0e9acc1..1dffb01b 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -177,6 +177,10 @@ components: type: string Name: type: string + BlockStoreConfig: + description: block storage config url encoded json + type: string + UpdateRepository: type: object properties: @@ -472,7 +476,6 @@ components: type: object additionalProperties: type: string - ObjectStats: type: object required: @@ -500,7 +503,6 @@ components: content_type: type: string description: Object media type - ObjectStatsList: type: object required: @@ -513,7 +515,93 @@ components: type: array items: $ref: "#/components/schemas/ObjectStats" - + BlockStoreConfig: + type: object + properties: + Type: + type: string + description: type of support storage type + enum: [ "local", "gs", "azure", "s3" ] + DefaultNamespacePrefix: + type: string + Local: + type: object + required: + - Path + properties: + Path: + type: string + Azure: + type: object + required: + - StorageAccount + - StorageAccessKey + - TryTimeout + properties: + StorageAccessKey: + type: string + StorageAccount: + type: string + TryTimeout: + type: integer + format: int64 + GS: + type: object + required: + - CredentialsJSON + - S3Endpoint + properties: + CredentialsJSON: + type: string + S3Endpoint: + type: string + S3: + type: object + required: + - Credentials + - DiscoverBucketRegion + - Region + - Endpoint + properties: + Credentials: + $ref: '#/components/schemas/Credential' + WebIdentity: + $ref: '#/components/schemas/WebIdentity' + DiscoverBucketRegion: + type: boolean + Endpoint: + type: string + ForcePathStyle: + type: boolean + Region: + type: string + WebIdentity: + properties: + SessionDuration: + type: integer + format: int64 + SessionExpiryWindow: + type: integer + format: int64 + S3AuthInfo: + type: object + description: S3AuthInfo holds S3-style authentication. + properties: + Credentials: + $ref: '#/components/schemas/Credential' + CredentialsFile: + type: string + Credential: + type: object + properties: + AccessKeyID: + $ref: '#/components/schemas/SecureString' + SecretAccessKey: + $ref: '#/components/schemas/SecureString' + SessionToken: + $ref: '#/components/schemas/SecureString' + SecureString: + type: string Pagination: type: object required: @@ -536,7 +624,6 @@ components: type: integer minimum: 0 description: Maximal number of entries per page - Error: type: object required: diff --git a/block/factory/build.go b/block/factory/build.go index 89bc1298..8ef9b5a4 100644 --- a/block/factory/build.go +++ b/block/factory/build.go @@ -26,6 +26,8 @@ const ( googleAuthCloudPlatform = "https://www.googleapis.com/auth/cloud-platform" ) +type BlockAdapterBuilder = func(context.Context, params.AdapterConfig) (block.Adapter, error) + func BuildBlockAdapter(ctx context.Context, c params.AdapterConfig) (block.Adapter, error) { blockstore := c.BlockstoreType() log.With("type", blockstore). diff --git a/cmd/daemon.go b/cmd/daemon.go index cc105aad..c3a04931 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -58,6 +58,7 @@ var daemonCmd = &cobra.Command{ fx_opt.Override(new(*config.DatabaseConfig), &cfg.Database), fx_opt.Override(new(params.AdapterConfig), &cfg.Blockstore), //blockstore + fx_opt.Override(new(factory.BlockAdapterBuilder), factory.BuildBlockAdapter), fx_opt.Override(new(block.Adapter), factory.BuildBlockAdapter), //database fx_opt.Override(new(*bun.DB), models.SetupDatabase), diff --git a/config/default.go b/config/default.go index c6af924e..37e81323 100644 --- a/config/default.go +++ b/config/default.go @@ -2,8 +2,6 @@ package config import ( "encoding/hex" - - "github.com/jiaozifs/jiaozifs/utils" ) var DefaultLocalBSPath = "~/.jiaozifs/blockstore" @@ -17,8 +15,7 @@ var defaultCfg = Config{ Listen: "http://127.0.0.1:34913", }, Blockstore: BlockStoreConfig{ - Type: "local", - DefaultNamespacePrefix: utils.String("data"), + Type: "local", Local: (*struct { Path string `mapstructure:"path"` ImportEnabled bool `mapstructure:"import_enabled"` diff --git a/controller/object_ctl.go b/controller/object_ctl.go index a65be493..e46f51a2 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -2,6 +2,7 @@ package controller import ( "context" + "encoding/json" "errors" "fmt" "io" @@ -10,6 +11,10 @@ import ( "net/http" "time" + "github.com/jiaozifs/jiaozifs/block/factory" + + "github.com/jiaozifs/jiaozifs/config" + "github.com/jiaozifs/jiaozifs/utils/hash" logging "github.com/ipfs/go-log/v2" @@ -35,12 +40,12 @@ var objLog = logging.Logger("object_ctl") type ObjectController struct { fx.In - BlockAdapter block.Adapter - - Repo models.IRepo + PublicBlkAdapter block.Adapter + AdapterBuilder factory.BlockAdapterBuilder + Repo models.IRepo } -func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, ownerName string, repositoryName string, params api.DeleteObjectParams) { //nolint +func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.DeleteObjectParams) { //nolint operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) @@ -101,7 +106,7 @@ func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsRes w.OK() } -func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, ownerName string, repositoryName string, params api.GetObjectParams) { //nolint +func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.GetObjectParams) { //nolint operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) @@ -172,7 +177,22 @@ func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsRespon w.Error(err) return } - reader, err := workTree.ReadBlob(ctx, oct.BlockAdapter, blob, params.Range) + + adpter := oct.PublicBlkAdapter + if !repository.UsePublicStorage { + var cfg = config.BlockStoreConfig{} + err = json.Unmarshal([]byte(repository.StorageAdapterParams), &cfg) + if err != nil { + w.BadRequest("storage config not json format") + return + } + adpter, err = oct.AdapterBuilder(ctx, &cfg) + if err != nil { + w.Error(fmt.Errorf("unable to build block storage")) + } + } + + reader, err := workTree.ReadBlob(ctx, adpter, repository.StorageNamespace, blob, params.Range) if err != nil { w.Error(err) return @@ -214,7 +234,7 @@ func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsRespon } } -func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, ownerName string, repositoryName string, params api.HeadObjectParams) { //nolint +func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.HeadObjectParams) { operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) @@ -401,6 +421,20 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes return } + adpter := oct.PublicBlkAdapter + if !repository.UsePublicStorage { + var cfg = config.BlockStoreConfig{} + err = json.Unmarshal([]byte(repository.StorageAdapterParams), &cfg) + if err != nil { + w.BadRequest("storage config not json format") + return + } + adpter, err = oct.AdapterBuilder(ctx, &cfg) + if err != nil { + w.Error(fmt.Errorf("unable to build block storage")) + } + } + path := versionmgr.CleanPath(params.Path) var response api.ObjectStats err = oct.Repo.Transaction(ctx, func(dRepo models.IRepo) error { @@ -410,7 +444,7 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes } // todo move write blob out of transaction - blob, err := workingTree.WriteBlob(ctx, oct.BlockAdapter, reader, r.ContentLength, models.DefaultLeafProperty()) + blob, err := workingTree.WriteBlob(ctx, adpter, repository.StorageNamespace, reader, r.ContentLength, models.DefaultLeafProperty()) if err != nil { return err } diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index 7d3b8d30..161a391e 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -2,16 +2,23 @@ package controller import ( "context" + "encoding/json" "errors" + "fmt" "io" "net/http" "regexp" "time" + logging "github.com/ipfs/go-log/v2" + + "github.com/jiaozifs/jiaozifs/config" + "github.com/google/uuid" openapi_types "github.com/oapi-codegen/runtime/types" + "github.com/jiaozifs/jiaozifs/block" "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/jiaozifs/jiaozifs/versionmgr" @@ -26,6 +33,7 @@ import ( const DefaultBranchName = "main" +var repoLog = logging.Logger("repo control") var maxNameLength = 20 var alphanumeric = regexp.MustCompile("^[a-zA-Z0-9_]*$") @@ -51,7 +59,8 @@ func CheckRepositoryName(name string) error { type RepositoryController struct { fx.In - Repo models.IRepo + Repo models.IRepo + PublicBlkAdapter block.Adapter } func (repositoryCtl RepositoryController) ListRepositoryOfAuthenticatedUser(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request) { @@ -110,10 +119,34 @@ func (repositoryCtl RepositoryController) CreateRepository(ctx context.Context, w.Error(err) return } + + var usePublicStorage = true + storageConfig := utils.StringValue(body.BlockStoreConfig) + repoID := uuid.New() + var storageNamespace string + if len(storageConfig) > 0 { + usePublicStorage = false + var cfg = config.BlockStoreConfig{} + err = json.Unmarshal([]byte(storageConfig), &cfg) + if err != nil { + w.BadRequest("storage config not json format") + return + } + + if cfg.BlockstoreType() == "local" { + repoLog.Infof("custom storage cnofig can not be local") + w.Forbidden() + return + } + storageNamespace = utils.StringValue(cfg.DefaultNamespacePrefix) + } else { + storageNamespace = fmt.Sprintf("%s://%s", repositoryCtl.PublicBlkAdapter.BlockstoreType(), repoID.String()) + } + //create default ref var createdRepo *models.Repository err = repositoryCtl.Repo.Transaction(ctx, func(repo models.IRepo) error { - repoID := uuid.New() + defaultRef := &models.Branches{ RepositoryID: repoID, CommitHash: hash.Hash{}, @@ -127,14 +160,17 @@ func (repositoryCtl RepositoryController) CreateRepository(ctx context.Context, return err } repository := &models.Repository{ - ID: repoID, - Name: body.Name, - Description: body.Description, - HEAD: defaultRef.Name, - OwnerID: operator.ID, // this api only create repo for operator - CreatorID: operator.ID, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), + ID: repoID, + Name: body.Name, + UsePublicStorage: usePublicStorage, + StorageAdapterParams: storageConfig, + StorageNamespace: storageNamespace, + Description: body.Description, + HEAD: defaultRef.Name, + OwnerID: operator.ID, // this api only create repo for operator + CreatorID: operator.ID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), } createdRepo, err = repositoryCtl.Repo.RepositoryRepo().Insert(ctx, repository) return err diff --git a/models/repository.go b/models/repository.go index fa45739a..ebe62331 100644 --- a/models/repository.go +++ b/models/repository.go @@ -8,14 +8,24 @@ import ( "github.com/uptrace/bun" ) +type StorageNamespace struct { + IsPublic bool `json:"is_public"` + Type string `json:"type"` +} + type Repository struct { bun.BaseModel `bun:"table:repositories"` ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` Name string `bun:"name,unique:name_owner_unique,notnull"` OwnerID uuid.UUID `bun:"owner_id,unique:name_owner_unique,type:uuid,notnull"` HEAD string `bun:"head,notnull"` - Description *string `bun:"description"` - CreatorID uuid.UUID `bun:"creator_id,type:uuid,notnull"` + + UsePublicStorage bool `bun:"use_public_storage,notnull"` + StorageAdapterParams string `bun:"storage_adapter,notnull"` + StorageNamespace string `bun:"storage_namespace"` + + Description *string `bun:"description"` + CreatorID uuid.UUID `bun:"creator_id,type:uuid,notnull"` CreatedAt time.Time `bun:"created_at"` UpdatedAt time.Time `bun:"updated_at"` diff --git a/versionmgr/worktree.go b/versionmgr/worktree.go index 21ed6022..04fbd798 100644 --- a/versionmgr/worktree.go +++ b/versionmgr/worktree.go @@ -78,10 +78,10 @@ func (workTree *WorkTree) RepositoryID() uuid.UUID { } // ReadBlob read blob content with range -func (workTree *WorkTree) ReadBlob(ctx context.Context, adapter block.Adapter, blob *models.Blob, rangeSpec *string) (io.ReadCloser, error) { +func (workTree *WorkTree) ReadBlob(ctx context.Context, adapter block.Adapter, storageNamespace string, blob *models.Blob, rangeSpec *string) (io.ReadCloser, error) { address := pathutil.PathOfHash(blob.CheckSum) pointer := block.ObjectPointer{ - StorageNamespace: adapter.BlockstoreType() + "://", + StorageNamespace: storageNamespace, IdentifierType: block.IdentifierTypeRelative, Identifier: address, } @@ -111,7 +111,7 @@ func (workTree *WorkTree) ReadBlob(ctx context.Context, adapter block.Adapter, b } // WriteBlob write blob content to storage -func (workTree *WorkTree) WriteBlob(ctx context.Context, adapter block.Adapter, body io.Reader, contentLength int64, properties models.Property) (*models.Blob, error) { +func (workTree *WorkTree) WriteBlob(ctx context.Context, adapter block.Adapter, storageNamespace string, body io.Reader, contentLength int64, properties models.Property) (*models.Blob, error) { // handle the upload itself hashReader := hash.NewHashingReader(body, hash.Md5) tempf, err := os.CreateTemp("", "*") @@ -137,7 +137,7 @@ func (workTree *WorkTree) WriteBlob(ctx context.Context, adapter block.Adapter, address := pathutil.PathOfHash(checkSum) err = adapter.Put(ctx, block.ObjectPointer{ - StorageNamespace: adapter.BlockstoreType() + "://", + StorageNamespace: storageNamespace, IdentifierType: block.IdentifierTypeRelative, Identifier: address, }, contentLength, tempf, block.PutOpts{}) diff --git a/versionmgr/worktree_test.go b/versionmgr/worktree_test.go index 019d60a3..fd7bc2a4 100644 --- a/versionmgr/worktree_test.go +++ b/versionmgr/worktree_test.go @@ -27,6 +27,7 @@ func TestTreeWriteBlob(t *testing.T) { repoID := uuid.New() adapter := mem.New(ctx) + namespace := "mem://data" objRepo := models.NewFileTree(db, repoID) workTree, err := NewWorkTree(ctx, objRepo, EmptyDirEntry) @@ -35,13 +36,13 @@ func TestTreeWriteBlob(t *testing.T) { binary := []byte("Build simple, secure, scalable systems with Go") bLen := int64(len(binary)) r := bytes.NewReader(binary) - blob, err := workTree.WriteBlob(ctx, adapter, r, bLen, models.DefaultLeafProperty()) + blob, err := workTree.WriteBlob(ctx, adapter, namespace, r, bLen, models.DefaultLeafProperty()) require.NoError(t, err) assert.Equal(t, bLen, blob.Size) assert.Equal(t, "99b91d4c517d0cded9506be9298b8d02", blob.Hash.Hex()) assert.Equal(t, "f3b39786b86a96372589aa1166966643", blob.CheckSum.Hex()) - reader, err := workTree.ReadBlob(ctx, adapter, blob, nil) + reader, err := workTree.ReadBlob(ctx, adapter, namespace, blob, nil) require.NoError(t, err) content, err := io.ReadAll(reader) require.NoError(t, err) @@ -55,6 +56,7 @@ func TestWorkTreeTreeOp(t *testing.T) { repoID := uuid.New() adapter := mem.New(ctx) + namespace := "mem://data" objRepo := models.NewFileTree(db, repoID) workTree, err := NewWorkTree(ctx, objRepo, EmptyDirEntry) @@ -63,7 +65,7 @@ func TestWorkTreeTreeOp(t *testing.T) { binary := []byte("Build simple, secure, scalable systems with Go") bLen := int64(len(binary)) r := bytes.NewReader(binary) - blob, err := workTree.WriteBlob(ctx, adapter, r, bLen, models.DefaultLeafProperty()) + blob, err := workTree.WriteBlob(ctx, adapter, namespace, r, bLen, models.DefaultLeafProperty()) require.NoError(t, err) err = workTree.AddLeaf(ctx, "a/b/c.txt", blob) @@ -78,7 +80,7 @@ func TestWorkTreeTreeOp(t *testing.T) { binary = []byte(`“At the time, no single team member knew Go, but within a month, everyone was writing in Go and we were building out the endpoints. ”`) bLen = int64(len(binary)) r = bytes.NewReader(binary) - blob, err = workTree.WriteBlob(ctx, adapter, r, bLen, models.DefaultLeafProperty()) + blob, err = workTree.WriteBlob(ctx, adapter, namespace, r, bLen, models.DefaultLeafProperty()) require.NoError(t, err) err = workTree.ReplaceLeaf(ctx, "a/b/c.txt", blob) @@ -149,6 +151,7 @@ func TestRemoveEntry(t *testing.T) { repoID := uuid.New() adapter := mem.New(ctx) + namespace := "mem://data" objRepo := models.NewFileTree(db, repoID) workTree, err := NewWorkTree(ctx, objRepo, EmptyDirEntry) @@ -157,7 +160,7 @@ func TestRemoveEntry(t *testing.T) { binary := []byte("Build simple, secure, scalable systems with Go") bLen := int64(len(binary)) r := bytes.NewReader(binary) - blob, err := workTree.WriteBlob(ctx, adapter, r, bLen, models.DefaultLeafProperty()) + blob, err := workTree.WriteBlob(ctx, adapter, namespace, r, bLen, models.DefaultLeafProperty()) require.NoError(t, err) err = workTree.AddLeaf(ctx, "a/b/c.txt", blob) @@ -168,7 +171,7 @@ func TestRemoveEntry(t *testing.T) { binary = []byte(`“At the time, no single team member knew Go, but within a month, everyone was writing in Go and we were building out the endpoints. ”`) bLen = int64(len(binary)) r = bytes.NewReader(binary) - blob, err = workTree.WriteBlob(ctx, adapter, r, bLen, models.DefaultLeafProperty()) + blob, err = workTree.WriteBlob(ctx, adapter, namespace, r, bLen, models.DefaultLeafProperty()) require.NoError(t, err) //add another branch From 154e85a4d0150f4f305d44bf5c18661a71bd5ad6 Mon Sep 17 00:00:00 2001 From: brown Date: Fri, 22 Dec 2023 11:21:22 +0800 Subject: [PATCH 107/210] test: add page test and branches match test --- models/branch_test.go | 17 ++++++++++++++-- utils/pagination.go | 10 ++++++---- utils/pagination_test.go | 43 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 utils/pagination_test.go diff --git a/models/branch_test.go b/models/branch_test.go index f28d8630..1c07ab73 100644 --- a/models/branch_test.go +++ b/models/branch_test.go @@ -69,9 +69,22 @@ func TestRefRepoInsert(t *testing.T) { require.True(t, cmp.Equal(secModel, sRef, dbTimeCmpOpt)) - list, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(utils.String(secModel.Name[:3]), models.PrefixMatch).SetAmount(1)) + // PrefixMatch + list1, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(utils.String(secModel.Name[:3]), models.PrefixMatch).SetAmount(1)) require.NoError(t, err) - require.Len(t, list, 1) + require.Len(t, list1, 1) + require.True(t, hasMore) + + // SuffixMatch + list2, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(utils.String(secModel.Name[3:]), models.SuffixMatch).SetAmount(1)) + require.NoError(t, err) + require.Len(t, list2, 1) + require.True(t, hasMore) + + // LikeMatch + list3, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(utils.String(secModel.Name[2:5]), models.LikeMatch).SetAmount(1)) + require.NoError(t, err) + require.Len(t, list3, 1) require.True(t, hasMore) newList, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetAfter(utils.String(branchModel.Name)).SetAmount(1)) diff --git a/utils/pagination.go b/utils/pagination.go index 5196d274..08190d4d 100644 --- a/utils/pagination.go +++ b/utils/pagination.go @@ -27,10 +27,12 @@ func PaginationFor(hasMore bool, results interface{}, fieldName string) PageMana } v := s.Index(pagination.Results - 1) token := v.FieldByName(fieldName) - switch fieldName { - case "UpdatedAt": - pagination.NextOffset = token.Interface().(time.Time).String() - case "Name": + switch token.Kind() { + case reflect.Struct: + if token.Type() == reflect.TypeOf(time.Time{}) { + pagination.NextOffset = token.Interface().(time.Time).String() + } + case reflect.String: pagination.NextOffset = token.Interface().(string) } return pagination diff --git a/utils/pagination_test.go b/utils/pagination_test.go new file mode 100644 index 00000000..9967b61f --- /dev/null +++ b/utils/pagination_test.go @@ -0,0 +1,43 @@ +package utils + +import ( + "testing" + "time" +) + +func TestPaginationFor(t *testing.T) { + // Test case 1: No more results + results1 := []struct { + UpdatedAt time.Time + Name string + }{} + pagination1 := PaginationFor(false, results1, "UpdatedAt") + if pagination1.HasMore || pagination1.Results != 0 || pagination1.NextOffset != "" { + t.Errorf("Test case 1 failed: Expected no more results") + } + // Test case 2: With more results and "UpdatedAt" as the field + results2 := []struct { + UpdatedAt time.Time + Name string + }{ + {time.Now(), "Item1"}, + {time.Now().Add(1 * time.Hour), "Item2"}, + } + pagination2 := PaginationFor(true, results2, "UpdatedAt") + if !pagination2.HasMore || pagination2.Results != 2 || pagination2.NextOffset == "" { + t.Errorf("Test case 2 failed: Expected more results with valid NextOffset") + } + + // Test case 3: With more results and "Name" as the field + results3 := []struct { + UpdatedAt time.Time + Name string + }{ + {time.Now(), "Item1"}, + {time.Now().Add(1 * time.Hour), "Item2"}, + } + pagination3 := PaginationFor(true, results3, "Name") + if !pagination3.HasMore || pagination3.Results != 2 || pagination3.NextOffset == "" { + t.Errorf("Test case 3 failed: Expected more results with valid NextOffset") + } +} From 075c560a26ca79c85644b39e58787f9e2a5e0fa7 Mon Sep 17 00:00:00 2001 From: brown Date: Fri, 22 Dec 2023 11:45:57 +0800 Subject: [PATCH 108/210] chore: modify default login url and add branch test where not covered --- config/default.go | 2 +- models/branch_test.go | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/config/default.go b/config/default.go index c6af924e..d9a6705a 100644 --- a/config/default.go +++ b/config/default.go @@ -42,7 +42,7 @@ var defaultCfg = Config{ LoginCookieNames []string `mapstructure:"login_cookie_names"` LogoutURL string `mapstructure:"logout_url"` }{RBAC: AuthRBACSimplified, - LoginURL: "api/v1/login", + LoginURL: "api/v1/auth/login", LoginFailedMessage: "", LoginCookieNames: nil, LogoutURL: "auth/logout", diff --git a/models/branch_test.go b/models/branch_test.go index 1c07ab73..18eb3b98 100644 --- a/models/branch_test.go +++ b/models/branch_test.go @@ -69,6 +69,12 @@ func TestRefRepoInsert(t *testing.T) { require.True(t, cmp.Equal(secModel, sRef, dbTimeCmpOpt)) + // ExactMatch + list, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(utils.String(secModel.Name), models.ExactMatch).SetAmount(1)) + require.NoError(t, err) + require.Len(t, list, 1) + require.True(t, hasMore) + // PrefixMatch list1, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(utils.String(secModel.Name[:3]), models.PrefixMatch).SetAmount(1)) require.NoError(t, err) From 0b1893bf99f7a4052b11ddb302c074e7e7e8c752 Mon Sep 17 00:00:00 2001 From: brown Date: Fri, 22 Dec 2023 13:06:07 +0800 Subject: [PATCH 109/210] chore: modify branch name --- .gitignore | 3 +++ models/branch_test.go | 32 +++++++++++++++++--------------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 9f63b47b..72d3df3f 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,6 @@ coverage.out # Go workspace file go.work + +#vscode +.vscode \ No newline at end of file diff --git a/models/branch_test.go b/models/branch_test.go index 18eb3b98..f2df2cec 100644 --- a/models/branch_test.go +++ b/models/branch_test.go @@ -23,7 +23,7 @@ func TestRefRepoInsert(t *testing.T) { branchModel := &models.Branches{} require.NoError(t, gofakeit.Struct(branchModel)) - branchModel.Name = "main/feat/aaa" + branchModel.Name = "feat/abc/aaa" newBrance, err := repo.Insert(ctx, branchModel) require.NoError(t, err) require.NotEqual(t, uuid.Nil, newBrance.ID) @@ -55,7 +55,7 @@ func TestRefRepoInsert(t *testing.T) { secModel := &models.Branches{} require.NoError(t, gofakeit.Struct(secModel)) secModel.RepositoryID = branch.RepositoryID - branchModel.Name = "main/feat/bbb" + branchModel.Name = "feat/bba/ccc" secRef, err := repo.Insert(ctx, secModel) require.NoError(t, err) require.NotEqual(t, uuid.Nil, secRef.ID) @@ -70,39 +70,41 @@ func TestRefRepoInsert(t *testing.T) { require.True(t, cmp.Equal(secModel, sRef, dbTimeCmpOpt)) // ExactMatch - list, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(utils.String(secModel.Name), models.ExactMatch).SetAmount(1)) + list1, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(utils.String(secModel.Name), models.ExactMatch).SetAmount(1)) require.NoError(t, err) - require.Len(t, list, 1) + require.Len(t, list1, 1) require.True(t, hasMore) // PrefixMatch - list1, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(utils.String(secModel.Name[:3]), models.PrefixMatch).SetAmount(1)) + list2, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(utils.String(secModel.Name[:3]), models.PrefixMatch).SetAmount(1)) require.NoError(t, err) - require.Len(t, list1, 1) + require.Len(t, list2, 1) require.True(t, hasMore) // SuffixMatch - list2, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(utils.String(secModel.Name[3:]), models.SuffixMatch).SetAmount(1)) + list3, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(utils.String(secModel.Name[3:]), models.SuffixMatch).SetAmount(1)) require.NoError(t, err) - require.Len(t, list2, 1) + require.Len(t, list3, 1) require.True(t, hasMore) // LikeMatch - list3, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(utils.String(secModel.Name[2:5]), models.LikeMatch).SetAmount(1)) + list4, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(utils.String(secModel.Name[2:4]), models.LikeMatch).SetAmount(1)) require.NoError(t, err) - require.Len(t, list3, 1) + require.Len(t, list4, 1) require.True(t, hasMore) - newList, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetAfter(utils.String(branchModel.Name)).SetAmount(1)) + // After + // require.Len(t, branch.Name, 3) + list5, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetAfter(utils.String("feat/abcd/aaa"))) require.NoError(t, err) - require.Len(t, newList, 1) - require.True(t, hasMore) + require.Len(t, list5, 1) + require.False(t, hasMore) affectedRows, err := repo.Delete(ctx, models.NewDeleteBranchParams().SetID(list[0].ID).SetRepositoryID(list[0].RepositoryID).SetName(list[0].Name)) require.NoError(t, err) require.Equal(t, int64(1), affectedRows) - list, _, err = repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID)) + list6, _, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID)) require.NoError(t, err) - require.Len(t, list, 1) + require.Len(t, list6, 1) } From 66a2b28f0aa93361f819d3edecc44d650fac0958 Mon Sep 17 00:00:00 2001 From: brown Date: Fri, 22 Dec 2023 14:17:58 +0800 Subject: [PATCH 110/210] fix: fix pagination of repo test --- .gitignore | 5 +---- models/repository_test.go | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 72d3df3f..3e8df224 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,4 @@ coverage.out # vendor/ # Go workspace file -go.work - -#vscode -.vscode \ No newline at end of file +go.work \ No newline at end of file diff --git a/models/repository_test.go b/models/repository_test.go index dacad005..ccbb6f24 100644 --- a/models/repository_test.go +++ b/models/repository_test.go @@ -3,6 +3,7 @@ package models_test import ( "context" "testing" + "time" "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" @@ -105,7 +106,7 @@ func TestRepositoryRepo_Insert(t *testing.T) { } { //after - repos, hasMore, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetAfter(utils.Time(secRepo.UpdatedAt)).SetAmount(1)) + repos, hasMore, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetAfter(utils.Time(time.Now())).SetAmount(1)) require.NoError(t, err) require.True(t, hasMore) require.Len(t, repos, 1) From 733bdc37a0e2af98ec80f4c5c15b6360dba16898 Mon Sep 17 00:00:00 2001 From: brown Date: Fri, 22 Dec 2023 15:42:14 +0800 Subject: [PATCH 111/210] chore: remove test comment --- models/branch_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/models/branch_test.go b/models/branch_test.go index f2df2cec..d7fbcaad 100644 --- a/models/branch_test.go +++ b/models/branch_test.go @@ -94,7 +94,6 @@ func TestRefRepoInsert(t *testing.T) { require.True(t, hasMore) // After - // require.Len(t, branch.Name, 3) list5, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetAfter(utils.String("feat/abcd/aaa"))) require.NoError(t, err) require.Len(t, list5, 1) From b2cff09a08834387d532b6a8c0dec2c5e4b1ce86 Mon Sep 17 00:00:00 2001 From: brown Date: Fri, 22 Dec 2023 15:57:17 +0800 Subject: [PATCH 112/210] fix: add new line at the end --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3e8df224..9f63b47b 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,4 @@ coverage.out # vendor/ # Go workspace file -go.work \ No newline at end of file +go.work From d62286b2a831067c0826d094bed9af5eec094260 Mon Sep 17 00:00:00 2001 From: brown Date: Fri, 22 Dec 2023 16:54:11 +0800 Subject: [PATCH 113/210] fix: fix branch model name --- models/branch_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/branch_test.go b/models/branch_test.go index d7fbcaad..7bb6bba7 100644 --- a/models/branch_test.go +++ b/models/branch_test.go @@ -51,11 +51,11 @@ func TestRefRepoInsert(t *testing.T) { require.NoError(t, err) require.Len(t, list, 1) - // second + // SecondModel secModel := &models.Branches{} require.NoError(t, gofakeit.Struct(secModel)) secModel.RepositoryID = branch.RepositoryID - branchModel.Name = "feat/bba/ccc" + secModel.Name = "feat/bba/ccc" secRef, err := repo.Insert(ctx, secModel) require.NoError(t, err) require.NotEqual(t, uuid.Nil, secRef.ID) From 9e4aa19ced581973b32cf272aa395ad401b8e83c Mon Sep 17 00:00:00 2001 From: brown Date: Fri, 22 Dec 2023 17:32:34 +0800 Subject: [PATCH 114/210] chore: modify params type --- controller/branch_ctl.go | 8 ++++++-- controller/repository_ctl.go | 32 ++++++++++++++++++++------------ models/branch.go | 8 ++++---- models/branch_test.go | 11 +++++------ models/repository.go | 8 ++++---- models/repository_test.go | 13 ++++++------- 6 files changed, 45 insertions(+), 35 deletions(-) diff --git a/controller/branch_ctl.go b/controller/branch_ctl.go index 85029513..d2afb501 100644 --- a/controller/branch_ctl.go +++ b/controller/branch_ctl.go @@ -75,8 +75,12 @@ func (bct BranchController) ListBranches(ctx context.Context, w *api.JiaozifsRes } listBranchParams := models.NewListBranchParams() - listBranchParams.SetName(params.Prefix, models.PrefixMatch) - listBranchParams.SetAfter(params.After) + if params.Prefix != nil && len(*params.Prefix) > 0 { + listBranchParams.SetName(*params.Prefix, models.PrefixMatch) + } + if params.After != nil && len(*params.After) > 0 { + listBranchParams.SetAfter(*params.After) + } pageAmount := utils.IntValue(params.Amount) if pageAmount > utils.DefaultMaxPerPage || pageAmount <= 0 { listBranchParams.SetAmount(utils.DefaultMaxPerPage) diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index 07b06019..fcc3413b 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -61,17 +61,21 @@ func (repositoryCtl RepositoryController) ListRepositoryOfAuthenticatedUser(ctx return } - listParams := models.NewListRepoParams() - listParams.SetName(params.Prefix, models.PrefixMatch) - listParams.SetAfter(params.After) + listRepoParams := models.NewListRepoParams() + if params.Prefix != nil && len(*params.Prefix) > 0 { + listRepoParams.SetName(*params.Prefix, models.PrefixMatch) + } + if params.After != nil { + listRepoParams.SetAfter(*params.After) + } pageAmount := utils.IntValue(params.Amount) if pageAmount > utils.DefaultMaxPerPage || pageAmount <= 0 { - listParams.SetAmount(utils.DefaultMaxPerPage) + listRepoParams.SetAmount(utils.DefaultMaxPerPage) } else { - listParams.SetAmount(pageAmount) + listRepoParams.SetAmount(pageAmount) } - repositories, hasMore, err := repositoryCtl.Repo.RepositoryRepo().List(ctx, listParams. + repositories, hasMore, err := repositoryCtl.Repo.RepositoryRepo().List(ctx, listRepoParams. SetOwnerID(operator.ID)) if err != nil { w.Error(err) @@ -120,17 +124,21 @@ func (repositoryCtl RepositoryController) ListRepository(ctx context.Context, w return } - listParams := models.NewListRepoParams().SetOwnerID(owner.ID) - listParams.SetName(params.Prefix, models.PrefixMatch) - listParams.SetAfter(params.After) + listRepoParams := models.NewListRepoParams().SetOwnerID(owner.ID) + if params.Prefix != nil && len(*params.Prefix) > 0 { + listRepoParams.SetName(*params.Prefix, models.PrefixMatch) + } + if params.After != nil { + listRepoParams.SetAfter(*params.After) + } pageAmount := utils.IntValue(params.Amount) if pageAmount > utils.DefaultMaxPerPage || pageAmount <= 0 { - listParams.SetAmount(utils.DefaultMaxPerPage) + listRepoParams.SetAmount(utils.DefaultMaxPerPage) } else { - listParams.SetAmount(pageAmount) + listRepoParams.SetAmount(pageAmount) } - repositories, hasMore, err := repositoryCtl.Repo.RepositoryRepo().List(ctx, listParams) + repositories, hasMore, err := repositoryCtl.Repo.RepositoryRepo().List(ctx, listRepoParams) if err != nil { w.Error(err) return diff --git a/models/branch.go b/models/branch.go index bfff7a7c..71cfcabb 100644 --- a/models/branch.go +++ b/models/branch.go @@ -108,14 +108,14 @@ func (gup *ListBranchParams) SetRepositoryID(repositoryID uuid.UUID) *ListBranch return gup } -func (gup *ListBranchParams) SetName(name *string, match MatchMode) *ListBranchParams { - gup.Name = name +func (gup *ListBranchParams) SetName(name string, match MatchMode) *ListBranchParams { + gup.Name = &name gup.NameMatch = match return gup } -func (gup *ListBranchParams) SetAfter(after *string) *ListBranchParams { - gup.After = after +func (gup *ListBranchParams) SetAfter(after string) *ListBranchParams { + gup.After = &after return gup } diff --git a/models/branch_test.go b/models/branch_test.go index 7bb6bba7..be27eeee 100644 --- a/models/branch_test.go +++ b/models/branch_test.go @@ -9,7 +9,6 @@ import ( "github.com/google/uuid" "github.com/jiaozifs/jiaozifs/models" "github.com/jiaozifs/jiaozifs/testhelper" - "github.com/jiaozifs/jiaozifs/utils" "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/stretchr/testify/require" ) @@ -70,31 +69,31 @@ func TestRefRepoInsert(t *testing.T) { require.True(t, cmp.Equal(secModel, sRef, dbTimeCmpOpt)) // ExactMatch - list1, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(utils.String(secModel.Name), models.ExactMatch).SetAmount(1)) + list1, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(secModel.Name, models.ExactMatch).SetAmount(1)) require.NoError(t, err) require.Len(t, list1, 1) require.True(t, hasMore) // PrefixMatch - list2, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(utils.String(secModel.Name[:3]), models.PrefixMatch).SetAmount(1)) + list2, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(secModel.Name[:3], models.PrefixMatch).SetAmount(1)) require.NoError(t, err) require.Len(t, list2, 1) require.True(t, hasMore) // SuffixMatch - list3, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(utils.String(secModel.Name[3:]), models.SuffixMatch).SetAmount(1)) + list3, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(secModel.Name[3:], models.SuffixMatch).SetAmount(1)) require.NoError(t, err) require.Len(t, list3, 1) require.True(t, hasMore) // LikeMatch - list4, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(utils.String(secModel.Name[2:4]), models.LikeMatch).SetAmount(1)) + list4, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(secModel.Name[2:4], models.LikeMatch).SetAmount(1)) require.NoError(t, err) require.Len(t, list4, 1) require.True(t, hasMore) // After - list5, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetAfter(utils.String("feat/abcd/aaa"))) + list5, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetAfter("feat/abcd/aaa")) require.NoError(t, err) require.Len(t, list5, 1) require.False(t, hasMore) diff --git a/models/repository.go b/models/repository.go index 56a9bdcb..e178d394 100644 --- a/models/repository.go +++ b/models/repository.go @@ -75,8 +75,8 @@ func (lrp *ListRepoParams) SetOwnerID(ownerID uuid.UUID) *ListRepoParams { return lrp } -func (lrp *ListRepoParams) SetName(name *string, match MatchMode) *ListRepoParams { - lrp.Name = name +func (lrp *ListRepoParams) SetName(name string, match MatchMode) *ListRepoParams { + lrp.Name = &name lrp.NameMatch = match return lrp } @@ -86,8 +86,8 @@ func (lrp *ListRepoParams) SetCreatorID(creatorID uuid.UUID) *ListRepoParams { return lrp } -func (lrp *ListRepoParams) SetAfter(after *time.Time) *ListRepoParams { - lrp.After = after +func (lrp *ListRepoParams) SetAfter(after time.Time) *ListRepoParams { + lrp.After = &after return lrp } diff --git a/models/repository_test.go b/models/repository_test.go index ccbb6f24..418f9afe 100644 --- a/models/repository_test.go +++ b/models/repository_test.go @@ -10,7 +10,6 @@ import ( "github.com/google/uuid" "github.com/jiaozifs/jiaozifs/models" "github.com/jiaozifs/jiaozifs/testhelper" - "github.com/jiaozifs/jiaozifs/utils" "github.com/stretchr/testify/require" ) @@ -54,32 +53,32 @@ func TestRepositoryRepo_Insert(t *testing.T) { { //exact adabbeb - repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName(utils.String("adabbeb"), models.PrefixMatch)) + repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("adabbeb", models.PrefixMatch)) require.NoError(t, err) require.Len(t, repos, 1) } { //prefix a - repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName(utils.String("a"), models.PrefixMatch)) + repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("a", models.PrefixMatch)) require.NoError(t, err) require.Len(t, repos, 2) } { //subfix b - repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName(utils.String("b"), models.SuffixMatch)) + repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("b", models.SuffixMatch)) require.NoError(t, err) require.Len(t, repos, 2) } { //like ab - repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName(utils.String("ab"), models.LikeMatch)) + repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("ab", models.LikeMatch)) require.NoError(t, err) require.Len(t, repos, 2) } { //like ab - repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName(utils.String("adabbeb"), models.LikeMatch)) + repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("adabbeb", models.LikeMatch)) require.NoError(t, err) require.Len(t, repos, 1) } @@ -106,7 +105,7 @@ func TestRepositoryRepo_Insert(t *testing.T) { } { //after - repos, hasMore, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetAfter(utils.Time(time.Now())).SetAmount(1)) + repos, hasMore, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetAfter(time.Now()).SetAmount(1)) require.NoError(t, err) require.True(t, hasMore) require.Len(t, repos, 1) From d23fa788186ff6909ae62c3b5152be83118091c0 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Fri, 22 Dec 2023 11:22:32 +0800 Subject: [PATCH 115/210] feat: add working repo --- api/api_impl/server.go | 19 +- api/custom_response.go | 1 - api/jiaozifs.gen.go | 100 ++-- api/swagger.yml | 4 +- auth/auth_middleware.go | 8 +- cmd/daemon.go | 18 +- cmd/version.go | 3 +- config/blockstore.go | 1 - config/config.go | 1 - controller/branch_ctl.go | 6 +- controller/commit_ctl.go | 26 +- controller/common_ctl.go | 9 +- controller/object_ctl.go | 188 ++----- controller/repository_ctl.go | 78 ++- controller/user_ctl.go | 15 +- controller/wip_ctl.go | 53 +- integrationtest/branch_test.go | 3 +- integrationtest/commit_test.go | 13 +- integrationtest/helper_test.go | 11 + integrationtest/objects_test.go | 23 +- integrationtest/repo_test.go | 27 +- integrationtest/user_test.go | 3 +- integrationtest/wip_object_test.go | 3 +- models/branch.go | 3 +- models/branch_test.go | 17 +- models/commit_test.go | 3 +- models/models.go | 3 +- models/models_test.go | 9 +- models/repo.go | 1 - models/repo_test.go | 3 +- models/tag.go | 1 - models/tag_test.go | 1 - models/tree_test.go | 6 +- models/user_test.go | 7 +- models/wip_test.go | 3 +- versionmgr/adapter_from_config.go | 23 + versionmgr/adapter_from_config_test.go | 30 ++ versionmgr/changes_test.go | 7 +- versionmgr/commit.go | 233 --------- versionmgr/commit_node.go | 68 ++- versionmgr/commit_node_test.go | 120 +++++ versionmgr/commit_test.go | 441 ---------------- versionmgr/commit_walker.go | 40 +- versionmgr/commit_walker_bfs.go | 25 +- versionmgr/commit_walker_bfs_filtered.go | 38 +- versionmgr/commit_walker_bfs_filtered_test.go | 115 +++++ versionmgr/commit_walker_bfs_test.go | 129 +++++ versionmgr/commit_walker_ctime.go | 27 +- versionmgr/commit_walker_ctime_test.go | 140 ++++++ versionmgr/commit_walker_test.go | 232 +++++++++ versionmgr/merge_base.go | 55 +- versionmgr/merge_base_test.go | 19 +- versionmgr/work_repo.go | 473 ++++++++++++++++++ versionmgr/work_repo_diff_test.go | 94 ++++ versionmgr/work_repo_merge_test.go | 262 ++++++++++ versionmgr/work_repo_test.go | 353 +++++++++++++ versionmgr/worktree.go | 86 +--- versionmgr/worktree_test.go | 91 +--- 58 files changed, 2376 insertions(+), 1395 deletions(-) create mode 100644 versionmgr/adapter_from_config.go create mode 100644 versionmgr/adapter_from_config_test.go delete mode 100644 versionmgr/commit.go create mode 100644 versionmgr/commit_node_test.go delete mode 100644 versionmgr/commit_test.go create mode 100644 versionmgr/commit_walker_bfs_filtered_test.go create mode 100644 versionmgr/commit_walker_bfs_test.go create mode 100644 versionmgr/commit_walker_ctime_test.go create mode 100644 versionmgr/commit_walker_test.go create mode 100644 versionmgr/work_repo.go create mode 100644 versionmgr/work_repo_diff_test.go create mode 100644 versionmgr/work_repo_merge_test.go create mode 100644 versionmgr/work_repo_test.go diff --git a/api/api_impl/server.go b/api/api_impl/server.go index 0afbad16..22c8ef2c 100644 --- a/api/api_impl/server.go +++ b/api/api_impl/server.go @@ -5,28 +5,21 @@ import ( "errors" "net" "net/http" - - "github.com/jiaozifs/jiaozifs/auth" - - "github.com/jiaozifs/jiaozifs/auth/crypt" + "net/url" "github.com/MadAppGang/httplog" - "github.com/rs/cors" - "github.com/flowchartsman/swaggerui" - - "github.com/gorilla/sessions" - - "github.com/jiaozifs/jiaozifs/models" - - "net/url" - "github.com/getkin/kin-openapi/openapi3filter" "github.com/go-chi/chi/v5" + "github.com/gorilla/sessions" logging "github.com/ipfs/go-log/v2" "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/auth" + "github.com/jiaozifs/jiaozifs/auth/crypt" "github.com/jiaozifs/jiaozifs/config" + "github.com/jiaozifs/jiaozifs/models" middleware "github.com/oapi-codegen/nethttp-middleware" + "github.com/rs/cors" "go.uber.org/fx" ) diff --git a/api/custom_response.go b/api/custom_response.go index 1e8ef069..f31d0304 100644 --- a/api/custom_response.go +++ b/api/custom_response.go @@ -6,7 +6,6 @@ import ( "net/http" "github.com/jiaozifs/jiaozifs/auth" - "github.com/jiaozifs/jiaozifs/models" ) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index cb7d0221..afac7b56 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -45,8 +45,8 @@ const ( // Defines values for RefType. const ( RefTypeBranch RefType = "branch" + RefTypeCommit RefType = "commit" RefTypeTag RefType = "tag" - RefTypeTest RefType = "test" RefTypeWip RefType = "wip" ) @@ -1915,7 +1915,7 @@ func NewGetEntriesInRefRequest(server string, owner string, repository string, p return nil, err } - operationPath := fmt.Sprintf("/repos/%s/%s/contents/", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/repos/%s/%s/contents", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -4374,7 +4374,7 @@ type ServerInterface interface { // (GET /repos/{owner}/{repository}/compare/{basehead}) GetCommitDiff(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, basehead string, params GetCommitDiffParams) // list entries in ref - // (GET /repos/{owner}/{repository}/contents/) + // (GET /repos/{owner}/{repository}/contents) GetEntriesInRef(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetEntriesInRefParams) // check if jiaozifs setup // (GET /setup) @@ -4511,7 +4511,7 @@ func (_ Unimplemented) GetCommitDiff(ctx context.Context, w *JiaozifsResponse, r } // list entries in ref -// (GET /repos/{owner}/{repository}/contents/) +// (GET /repos/{owner}/{repository}/contents) func (_ Unimplemented) GetEntriesInRef(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetEntriesInRefParams) { w.WriteHeader(http.StatusNotImplemented) } @@ -6242,7 +6242,7 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Get(options.BaseURL+"/repos/{owner}/{repository}/compare/{basehead}", wrapper.GetCommitDiff) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/repos/{owner}/{repository}/contents/", wrapper.GetEntriesInRef) + r.Get(options.BaseURL+"/repos/{owner}/{repository}/contents", wrapper.GetEntriesInRef) }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/setup", wrapper.GetSetupState) @@ -6310,51 +6310,51 @@ var swaggerSpec = []string{ "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", - "AAD//xLQ6hqZXwAA", + "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", + "///DhHZ9ml8AAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 1dffb01b..c9621f5f 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -123,7 +123,7 @@ components: type: string RefType: type: string - enum: ["branch", "wip","tag", "test"] + enum: ["branch", "wip","tag", "commit"] Branch: type: object required: @@ -1082,7 +1082,7 @@ paths: 403: description: Forbidden - /repos/{owner}/{repository}/contents/: + /repos/{owner}/{repository}/contents: parameters: - in: path name: owner diff --git a/auth/auth_middleware.go b/auth/auth_middleware.go index 75075fa0..b4459180 100644 --- a/auth/auth_middleware.go +++ b/auth/auth_middleware.go @@ -6,14 +6,12 @@ import ( "net/http" "strings" - "github.com/jiaozifs/jiaozifs/auth/crypt" - - "github.com/gorilla/sessions" - "github.com/jiaozifs/jiaozifs/models" - "github.com/getkin/kin-openapi/openapi3" "github.com/getkin/kin-openapi/routers" "github.com/getkin/kin-openapi/routers/legacy" + "github.com/gorilla/sessions" + "github.com/jiaozifs/jiaozifs/auth/crypt" + "github.com/jiaozifs/jiaozifs/models" ) const ( diff --git a/cmd/daemon.go b/cmd/daemon.go index c3a04931..93e5e621 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -3,25 +3,18 @@ package cmd import ( "context" - "github.com/jiaozifs/jiaozifs/auth/crypt" - - "github.com/jiaozifs/jiaozifs/version" - "github.com/gorilla/sessions" - "github.com/jiaozifs/jiaozifs/auth" - - "github.com/jiaozifs/jiaozifs/block/params" - - "github.com/jiaozifs/jiaozifs/block" - "github.com/jiaozifs/jiaozifs/block/factory" - logging "github.com/ipfs/go-log/v2" apiImpl "github.com/jiaozifs/jiaozifs/api/api_impl" + "github.com/jiaozifs/jiaozifs/auth" + "github.com/jiaozifs/jiaozifs/auth/crypt" + "github.com/jiaozifs/jiaozifs/block/params" "github.com/jiaozifs/jiaozifs/config" "github.com/jiaozifs/jiaozifs/fx_opt" "github.com/jiaozifs/jiaozifs/models" "github.com/jiaozifs/jiaozifs/models/migrations" "github.com/jiaozifs/jiaozifs/utils" + "github.com/jiaozifs/jiaozifs/version" "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/uptrace/bun" @@ -57,9 +50,6 @@ var daemonCmd = &cobra.Command{ fx_opt.Override(new(*config.AuthConfig), &cfg.Auth), fx_opt.Override(new(*config.DatabaseConfig), &cfg.Database), fx_opt.Override(new(params.AdapterConfig), &cfg.Blockstore), - //blockstore - fx_opt.Override(new(factory.BlockAdapterBuilder), factory.BuildBlockAdapter), - fx_opt.Override(new(block.Adapter), factory.BuildBlockAdapter), //database fx_opt.Override(new(*bun.DB), models.SetupDatabase), fx_opt.Override(new(models.IRepo), func(db *bun.DB) models.IRepo { diff --git a/cmd/version.go b/cmd/version.go index d77bda07..773165af 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -3,9 +3,8 @@ package cmd import ( "fmt" - "github.com/jiaozifs/jiaozifs/version" - "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/version" "github.com/spf13/cobra" ) diff --git a/config/blockstore.go b/config/blockstore.go index 704093cf..ee216c8a 100644 --- a/config/blockstore.go +++ b/config/blockstore.go @@ -5,7 +5,6 @@ import ( "time" "github.com/jiaozifs/jiaozifs/block/params" - "github.com/mitchellh/go-homedir" ) diff --git a/config/config.go b/config/config.go index 163eca14..71354702 100644 --- a/config/config.go +++ b/config/config.go @@ -6,7 +6,6 @@ import ( "path" "github.com/mitchellh/go-homedir" - ms "github.com/mitchellh/mapstructure" "github.com/spf13/cobra" "github.com/spf13/viper" diff --git a/controller/branch_ctl.go b/controller/branch_ctl.go index f697cfbc..7d1ba431 100644 --- a/controller/branch_ctl.go +++ b/controller/branch_ctl.go @@ -9,12 +9,10 @@ import ( "strings" "time" - "github.com/jiaozifs/jiaozifs/utils/hash" - - "github.com/jiaozifs/jiaozifs/auth" - "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/auth" "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/utils/hash" "go.uber.org/fx" ) diff --git a/controller/commit_ctl.go b/controller/commit_ctl.go index 0435ad7b..a812f69b 100644 --- a/controller/commit_ctl.go +++ b/controller/commit_ctl.go @@ -7,23 +7,21 @@ import ( "net/http" "strings" - "github.com/jiaozifs/jiaozifs/utils/hash" - + "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/auth" - + "github.com/jiaozifs/jiaozifs/block/params" + "github.com/jiaozifs/jiaozifs/models" "github.com/jiaozifs/jiaozifs/utils" - + "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/jiaozifs/jiaozifs/versionmgr" - - "github.com/jiaozifs/jiaozifs/api" - "github.com/jiaozifs/jiaozifs/models" "go.uber.org/fx" ) type CommitController struct { fx.In - Repo models.IRepo + Repo models.IRepo + PublicStorageConfig params.AdapterConfig } func (commitCtl CommitController) GetEntriesInRef(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.GetEntriesInRefParams) { @@ -138,31 +136,31 @@ func (commitCtl CommitController) GetCommitDiff(ctx context.Context, w *api.Jiao return } - bashCommitHash, err := hex.DecodeString(baseHead[0]) + toCommitHash, err := hex.DecodeString(baseHead[1]) if err != nil { w.Error(err) return } - toCommitHash, err := hex.DecodeString(baseHead[1]) + + workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repository, commitCtl.Repo, commitCtl.PublicStorageConfig) if err != nil { w.Error(err) return } - bashCommit, err := commitCtl.Repo.CommitRepo(repository.ID).Commit(ctx, bashCommitHash) + err = workRepo.CheckOut(ctx, versionmgr.InCommit, baseHead[0]) if err != nil { w.Error(err) return } - path := versionmgr.CleanPath(utils.StringValue(params.Path)) - commitOp := versionmgr.NewCommitOp(commitCtl.Repo, repository.ID, bashCommit) - changes, err := commitOp.DiffCommit(ctx, toCommitHash) + changes, err := workRepo.DiffCommit(ctx, toCommitHash) if err != nil { w.Error(err) return } + path := versionmgr.CleanPath(utils.StringValue(params.Path)) var changesResp []api.Change err = changes.ForEach(func(change versionmgr.IChange) error { action, err := change.Action() diff --git a/controller/common_ctl.go b/controller/common_ctl.go index 3e4b9b1e..4875cf59 100644 --- a/controller/common_ctl.go +++ b/controller/common_ctl.go @@ -4,14 +4,11 @@ import ( "context" "net/http" - logging "github.com/ipfs/go-log/v2" - - "github.com/jiaozifs/jiaozifs/utils" - "github.com/go-openapi/swag" - "github.com/jiaozifs/jiaozifs/config" - + logging "github.com/ipfs/go-log/v2" "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/config" + "github.com/jiaozifs/jiaozifs/utils" "github.com/jiaozifs/jiaozifs/version" "go.uber.org/fx" ) diff --git a/controller/object_ctl.go b/controller/object_ctl.go index e46f51a2..c229ec08 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -2,7 +2,6 @@ package controller import ( "context" - "encoding/json" "errors" "fmt" "io" @@ -11,27 +10,17 @@ import ( "net/http" "time" - "github.com/jiaozifs/jiaozifs/block/factory" - - "github.com/jiaozifs/jiaozifs/config" - - "github.com/jiaozifs/jiaozifs/utils/hash" - - logging "github.com/ipfs/go-log/v2" - "github.com/go-openapi/swag" + logging "github.com/ipfs/go-log/v2" + "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/auth" + "github.com/jiaozifs/jiaozifs/block/params" + "github.com/jiaozifs/jiaozifs/models" "github.com/jiaozifs/jiaozifs/models/filemode" - "github.com/jiaozifs/jiaozifs/versionmgr" - - "github.com/jiaozifs/jiaozifs/block" - "github.com/jiaozifs/jiaozifs/utils" + "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/jiaozifs/jiaozifs/utils/httputil" - - "github.com/jiaozifs/jiaozifs/models" - - "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/versionmgr" "go.uber.org/fx" ) @@ -40,9 +29,8 @@ var objLog = logging.Logger("object_ctl") type ObjectController struct { fx.In - PublicBlkAdapter block.Adapter - AdapterBuilder factory.BlockAdapterBuilder - Repo models.IRepo + PublicStorageConfig params.AdapterConfig + Repo models.IRepo } func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.DeleteObjectParams) { //nolint @@ -124,45 +112,25 @@ func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsRespon return } - repository, err := oct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) + repoModel, err := oct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) if err != nil { w.Error(err) return } - treeHash := hash.EmptyHash - if params.Type == "wip" { - ref, err := oct.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(params.RefName)) - if err != nil { - w.Error(err) - return - } - wip, err := oct.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetCreatorID(operator.ID).SetRepositoryID(repository.ID).SetRefID(ref.ID)) - if err != nil { - w.Error(err) - return - } - treeHash = wip.CurrentTree - } else if params.Type == "branch" { - ref, err := oct.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(params.RefName)) - if err != nil { - w.Error(err) - return - } - if !ref.CommitHash.IsEmpty() { - commit, err := oct.Repo.CommitRepo(repository.ID).Commit(ctx, ref.CommitHash) - if err != nil { - w.Error(err) - return - } - treeHash = commit.TreeHash - } - } else { - w.BadRequest("not support type") + workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repoModel, oct.Repo, oct.PublicStorageConfig) + if err != nil { + w.Error(err) return } - workTree, err := versionmgr.NewWorkTree(ctx, oct.Repo.FileTreeRepo(repository.ID), models.NewRootTreeEntry(treeHash)) + err = workRepo.CheckOut(ctx, versionmgr.WorkRepoState(params.Type), params.RefName) + if err != nil { + w.Error(err) + return + } + + workTree, err := workRepo.RootTree(ctx) if err != nil { w.Error(err) return @@ -178,21 +146,7 @@ func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsRespon return } - adpter := oct.PublicBlkAdapter - if !repository.UsePublicStorage { - var cfg = config.BlockStoreConfig{} - err = json.Unmarshal([]byte(repository.StorageAdapterParams), &cfg) - if err != nil { - w.BadRequest("storage config not json format") - return - } - adpter, err = oct.AdapterBuilder(ctx, &cfg) - if err != nil { - w.Error(fmt.Errorf("unable to build block storage")) - } - } - - reader, err := workTree.ReadBlob(ctx, adpter, repository.StorageNamespace, blob, params.Range) + reader, err := workRepo.ReadBlob(ctx, blob, params.Range) if err != nil { w.Error(err) return @@ -252,47 +206,25 @@ func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsRespo return } - repository, err := oct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) + repoModel, err := oct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) if err != nil { w.Error(err) return } - treeHash := hash.EmptyHash - if params.Type == "wip" { - ref, err := oct.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(params.RefName)) - if err != nil { - w.Error(err) - return - } + workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repoModel, oct.Repo, oct.PublicStorageConfig) + if err != nil { + w.Error(err) + return + } - wip, err := oct.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetCreatorID(operator.ID).SetRepositoryID(repository.ID).SetRefID(ref.ID)) - if err != nil { - w.Error(err) - return - } - treeHash = wip.CurrentTree - } else if params.Type == "branch" { - ref, err := oct.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(params.RefName)) - if err != nil { - w.Error(err) - return - } - if !ref.CommitHash.IsEmpty() { - commit, err := oct.Repo.CommitRepo(repository.ID).Commit(ctx, ref.CommitHash) - if err != nil { - w.Error(err) - return - } - treeHash = commit.TreeHash - } - } else { - w.BadRequest("not support type") + err = workRepo.CheckOut(ctx, versionmgr.WorkRepoState(params.Type), params.RefName) + if err != nil { + w.Error(err) return } - fileRepo := oct.Repo.FileTreeRepo(repository.ID) - workTree, err := versionmgr.NewWorkTree(ctx, fileRepo, models.NewRootTreeEntry(treeHash)) + workTree, err := workRepo.RootTree(ctx) if err != nil { w.Error(err) return @@ -397,78 +329,56 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes return } - repository, err := oct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) + repoModel, err := oct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) if err != nil { w.Error(err) return } - ref, err := oct.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetName(params.RefName).SetRepositoryID(repository.ID)) + workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repoModel, oct.Repo, oct.PublicStorageConfig) if err != nil { w.Error(err) return } - wip, err := oct.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetCreatorID(operator.ID).SetRepositoryID(repository.ID).SetRefID(ref.ID)) + err = workRepo.CheckOut(ctx, versionmgr.InWip, params.RefName) if err != nil { w.Error(err) return } - stash, err := oct.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetID(wip.ID)) + workTree, err := workRepo.RootTree(ctx) if err != nil { w.Error(err) return } - adpter := oct.PublicBlkAdapter - if !repository.UsePublicStorage { - var cfg = config.BlockStoreConfig{} - err = json.Unmarshal([]byte(repository.StorageAdapterParams), &cfg) - if err != nil { - w.BadRequest("storage config not json format") - return - } - adpter, err = oct.AdapterBuilder(ctx, &cfg) - if err != nil { - w.Error(fmt.Errorf("unable to build block storage")) - } + blob, err := workRepo.WriteBlob(ctx, reader, r.ContentLength, models.DefaultLeafProperty()) + if err != nil { + w.Error(err) + return } path := versionmgr.CleanPath(params.Path) - var response api.ObjectStats err = oct.Repo.Transaction(ctx, func(dRepo models.IRepo) error { - workingTree, err := versionmgr.NewWorkTree(ctx, dRepo.FileTreeRepo(repository.ID), models.NewRootTreeEntry(stash.CurrentTree)) - if err != nil { - return err - } - - // todo move write blob out of transaction - blob, err := workingTree.WriteBlob(ctx, adpter, repository.StorageNamespace, reader, r.ContentLength, models.DefaultLeafProperty()) + err = workTree.AddLeaf(ctx, path, blob) if err != nil { return err } - - err = workingTree.AddLeaf(ctx, path, blob) - if err != nil { - return err - } - response = api.ObjectStats{ - Checksum: blob.CheckSum.Hex(), - Mtime: time.Now().Unix(), - Path: path, - PathMode: utils.Uint32(uint32(filemode.Regular)), - SizeBytes: swag.Int64(blob.Size), - ContentType: &contentType, - Metadata: &api.ObjectUserMetadata{}, - } - return dRepo.WipRepo().UpdateByID(ctx, models.NewUpdateWipParams(stash.ID).SetCurrentTree(workingTree.Root().Hash())) + return dRepo.WipRepo().UpdateByID(ctx, models.NewUpdateWipParams(workRepo.CurWip().ID).SetCurrentTree(workTree.Root().Hash())) }) - if err != nil { w.Error(err) return } - w.JSON(response, http.StatusCreated) + w.JSON(api.ObjectStats{ + Checksum: blob.CheckSum.Hex(), + Mtime: time.Now().Unix(), + Path: path, + PathMode: utils.Uint32(uint32(filemode.Regular)), + SizeBytes: swag.Int64(blob.Size), + ContentType: &contentType, + Metadata: &api.ObjectUserMetadata{}, + }, http.StatusCreated) } diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index 161a391e..50c9cfa8 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -10,24 +10,17 @@ import ( "regexp" "time" - logging "github.com/ipfs/go-log/v2" - - "github.com/jiaozifs/jiaozifs/config" - "github.com/google/uuid" - - openapi_types "github.com/oapi-codegen/runtime/types" - - "github.com/jiaozifs/jiaozifs/block" - "github.com/jiaozifs/jiaozifs/utils/hash" - - "github.com/jiaozifs/jiaozifs/versionmgr" - - "github.com/jiaozifs/jiaozifs/auth" - + logging "github.com/ipfs/go-log/v2" "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/auth" + "github.com/jiaozifs/jiaozifs/block/params" + "github.com/jiaozifs/jiaozifs/config" "github.com/jiaozifs/jiaozifs/models" "github.com/jiaozifs/jiaozifs/utils" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/jiaozifs/jiaozifs/versionmgr" + openapi_types "github.com/oapi-codegen/runtime/types" "go.uber.org/fx" ) @@ -59,8 +52,8 @@ func CheckRepositoryName(name string) error { type RepositoryController struct { fx.In - Repo models.IRepo - PublicBlkAdapter block.Adapter + Repo models.IRepo + PublicStorageConfig params.AdapterConfig } func (repositoryCtl RepositoryController) ListRepositoryOfAuthenticatedUser(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request) { @@ -140,42 +133,41 @@ func (repositoryCtl RepositoryController) CreateRepository(ctx context.Context, } storageNamespace = utils.StringValue(cfg.DefaultNamespacePrefix) } else { - storageNamespace = fmt.Sprintf("%s://%s", repositoryCtl.PublicBlkAdapter.BlockstoreType(), repoID.String()) + storageNamespace = fmt.Sprintf("%s://%s", repositoryCtl.PublicStorageConfig.BlockstoreType(), repoID.String()) + } + + defaultRef := &models.Branches{ + RepositoryID: repoID, + CommitHash: hash.Hash{}, + Name: DefaultBranchName, + CreatorID: operator.ID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + repository := &models.Repository{ + ID: repoID, + Name: body.Name, + UsePublicStorage: usePublicStorage, + StorageAdapterParams: storageConfig, + StorageNamespace: storageNamespace, + Description: body.Description, + HEAD: DefaultBranchName, + OwnerID: operator.ID, // this api only create repo for operator + CreatorID: operator.ID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), } //create default ref var createdRepo *models.Repository err = repositoryCtl.Repo.Transaction(ctx, func(repo models.IRepo) error { - - defaultRef := &models.Branches{ - RepositoryID: repoID, - CommitHash: hash.Hash{}, - Name: DefaultBranchName, - CreatorID: operator.ID, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - defaultRef, err := repositoryCtl.Repo.BranchRepo().Insert(ctx, defaultRef) + _, err := repositoryCtl.Repo.BranchRepo().Insert(ctx, defaultRef) if err != nil { return err } - repository := &models.Repository{ - ID: repoID, - Name: body.Name, - UsePublicStorage: usePublicStorage, - StorageAdapterParams: storageConfig, - StorageNamespace: storageNamespace, - Description: body.Description, - HEAD: defaultRef.Name, - OwnerID: operator.ID, // this api only create repo for operator - CreatorID: operator.ID, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } createdRepo, err = repositoryCtl.Repo.RepositoryRepo().Insert(ctx, repository) return err }) - if err != nil { w.Error(err) return @@ -333,8 +325,8 @@ func (repositoryCtl RepositoryController) GetCommitsInRepository(ctx context.Con } var commits []api.Commit - commitNode := versionmgr.NewCommitNode(ctx, commit, repositoryCtl.Repo.CommitRepo(repository.ID)) - iter := versionmgr.NewCommitPreorderIter(commitNode, nil, nil) + commitNode := versionmgr.NewWrapCommitNode(repositoryCtl.Repo.CommitRepo(repository.ID), commit) + iter := versionmgr.NewCommitPreorderIter(ctx, commitNode, nil, nil) for { commit, err := iter.Next() if err == nil { diff --git a/controller/user_ctl.go b/controller/user_ctl.go index e1055253..36df3a69 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -7,20 +7,15 @@ import ( "time" "github.com/go-openapi/swag" - "golang.org/x/crypto/bcrypt" - - openapitypes "github.com/oapi-codegen/runtime/types" - - logging "github.com/ipfs/go-log/v2" - "github.com/gorilla/sessions" - - "github.com/jiaozifs/jiaozifs/config" - "github.com/jiaozifs/jiaozifs/models" - + logging "github.com/ipfs/go-log/v2" "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/auth" + "github.com/jiaozifs/jiaozifs/config" + "github.com/jiaozifs/jiaozifs/models" + openapitypes "github.com/oapi-codegen/runtime/types" "go.uber.org/fx" + "golang.org/x/crypto/bcrypt" ) var userCtlLog = logging.Logger("user_ctl") diff --git a/controller/wip_ctl.go b/controller/wip_ctl.go index 869a8d90..351635cc 100644 --- a/controller/wip_ctl.go +++ b/controller/wip_ctl.go @@ -10,22 +10,21 @@ import ( "strings" "time" - "github.com/jiaozifs/jiaozifs/utils/hash" - - "github.com/jiaozifs/jiaozifs/auth" - "github.com/jiaozifs/jiaozifs/versionmgr" - - "github.com/jiaozifs/jiaozifs/utils" - "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/auth" + "github.com/jiaozifs/jiaozifs/block/params" "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/utils" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/jiaozifs/jiaozifs/versionmgr" "go.uber.org/fx" ) type WipController struct { fx.In - Repo models.IRepo + Repo models.IRepo + PublicStorageConfig params.AdapterConfig } // CreateWip create wip of branch @@ -196,55 +195,25 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon return } - ref, err := wipCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetName(params.RefName).SetRepositoryID(repository.ID)) + workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repository, wipCtl.Repo, wipCtl.PublicStorageConfig) if err != nil { w.Error(err) return } - wip, err := wipCtl.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetRefID(ref.ID).SetCreatorID(operator.ID).SetRepositoryID(repository.ID)) + err = workRepo.CheckOut(ctx, versionmgr.InWip, params.RefName) if err != nil { w.Error(err) return } - if !bytes.Equal(ref.CommitHash, wip.BaseCommit) { - w.Error(fmt.Errorf("base commit not equal with branch, please update wip")) - return - } - - var commit *models.Commit - if !ref.CommitHash.IsEmpty() { - commit, err = wipCtl.Repo.CommitRepo(repository.ID).Commit(ctx, ref.CommitHash) - if err != nil { - w.Error(err) - return - } - } - - //add commit - err = wipCtl.Repo.Transaction(ctx, func(repo models.IRepo) error { - commitOp := versionmgr.NewCommitOp(repo, repository.ID, commit) - commit, err := commitOp.AddCommit(ctx, operator, wip.ID, params.Msg) - if err != nil { - return err - } - - wip.BaseCommit = commit.Commit().Hash //set for response - wip.CurrentTree = commit.Commit().TreeHash - err = repo.WipRepo().UpdateByID(ctx, models.NewUpdateWipParams(wip.ID).SetBaseCommit(wip.BaseCommit).SetCurrentTree(wip.CurrentTree)) - if err != nil { - return err - } - - return repo.BranchRepo().UpdateByID(ctx, models.NewUpdateBranchParams(ref.ID).SetCommitHash(commit.Commit().Hash)) - }) + _, err = workRepo.CommitChanges(ctx, params.Msg) if err != nil { w.Error(err) return } - w.JSON(wip, http.StatusCreated) + w.JSON(workRepo.CurWip(), http.StatusCreated) } // DeleteWip delete active working in process operator only can delete himself wip diff --git a/integrationtest/branch_test.go b/integrationtest/branch_test.go index 660c670c..8495daa9 100644 --- a/integrationtest/branch_test.go +++ b/integrationtest/branch_test.go @@ -5,10 +5,9 @@ import ( "net/http" "strings" - "github.com/jiaozifs/jiaozifs/controller" - "github.com/jiaozifs/jiaozifs/api" apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" + "github.com/jiaozifs/jiaozifs/controller" "github.com/smartystreets/goconvey/convey" ) diff --git a/integrationtest/commit_test.go b/integrationtest/commit_test.go index 72c0d5f3..cf7c3698 100644 --- a/integrationtest/commit_test.go +++ b/integrationtest/commit_test.go @@ -4,10 +4,9 @@ import ( "context" "net/http" - "github.com/jiaozifs/jiaozifs/utils" - "github.com/jiaozifs/jiaozifs/api" apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" + "github.com/jiaozifs/jiaozifs/utils" "github.com/smartystreets/goconvey/convey" ) @@ -126,16 +125,6 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { commitWip(ctx, c, client, "commit kitty first changes", userName, repoName, branchName, "test") - c.Convey("get entries with no type", func() { - resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ - Path: utils.String("g"), - Ref: utils.String(branchName), - Type: api.RefTypeTest, - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) - }) - c.Convey("get branch entries", func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors diff --git a/integrationtest/helper_test.go b/integrationtest/helper_test.go index e40030a7..0a8771d4 100644 --- a/integrationtest/helper_test.go +++ b/integrationtest/helper_test.go @@ -41,6 +41,17 @@ func Daemon(ctx context.Context, writer io.Writer, jzHome string, listen string) return cmd.RootCmd().ExecuteContext(ctx) } +func TestDoubleInit(t *testing.T) { //nolint + url := "http://127.0.0.1:1234" + ctx := context.Background() + tmpDir, err := os.MkdirTemp(os.TempDir(), "*") + require.NoError(t, err) + require.NoError(t, InitCmd(ctx, tmpDir, url, "")) + err = InitCmd(ctx, tmpDir, url, "") + require.Error(t, err) + require.Contains(t, err.Error(), "config already exit") +} + type Closer func() func SetupDaemon(t *testing.T, ctx context.Context) (string, Closer) { //nolint diff --git a/integrationtest/objects_test.go b/integrationtest/objects_test.go index a0e1bb38..7b1a192c 100644 --- a/integrationtest/objects_test.go +++ b/integrationtest/objects_test.go @@ -8,10 +8,9 @@ import ( "io" "net/http" - "github.com/jiaozifs/jiaozifs/utils/hash" - "github.com/jiaozifs/jiaozifs/api" apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" + "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/smartystreets/goconvey/convey" ) @@ -116,16 +115,6 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { //commit object to branch commitWip(ctx, c, client, "commit wip", userName, repoName, branchName, "test commit msg") - c.Convey("head object with no type", func() { - resp, err := client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ - RefName: branchName, - Path: "a.bin", - Type: api.RefTypeTest, - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) - }) - c.Convey("head object", func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors @@ -212,16 +201,6 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { }) }) - c.Convey("get object with no type", func() { - resp, err := client.GetObject(ctx, userName, repoName, &api.GetObjectParams{ - RefName: branchName, - Path: "a.bin", - Type: api.RefTypeTest, - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) - }) - c.Convey("get object", func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index 2ae37f3e..78b9e50f 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -5,13 +5,10 @@ import ( "fmt" "net/http" + "github.com/jiaozifs/jiaozifs/api" apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" - "github.com/jiaozifs/jiaozifs/controller" - "github.com/jiaozifs/jiaozifs/utils" - - "github.com/jiaozifs/jiaozifs/api" "github.com/smartystreets/goconvey/convey" ) @@ -33,6 +30,28 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) }) + c.Convey("config error", func() { + cfg := `{"Type":"local",DefaultNamespacePrefix":null,"Local":{"Path":"~/.jiaozifs/blockstore","ImportEnabled":false,"ImportHidden":false,"AllowedExternalPrefixes":null},"S3":null,"Azure":null,"GS":null}` + resp, err := client.CreateRepository(ctx, api.CreateRepository{ + Description: utils.String("test resp"), + Name: "happygo", + BlockStoreConfig: utils.String(cfg), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + + c.Convey("local not support", func() { + cfg := `{"Type":"local","DefaultNamespacePrefix":null,"Local":{"Path":"~/.jiaozifs/blockstore","ImportEnabled":false,"ImportHidden":false,"AllowedExternalPrefixes":null},"S3":null,"Azure":null,"GS":null}` + resp, err := client.CreateRepository(ctx, api.CreateRepository{ + Description: utils.String("test resp"), + Name: "happygo", + BlockStoreConfig: utils.String(cfg), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + c.Convey("success create repo name", func() { resp, err := client.CreateRepository(ctx, api.CreateRepository{ Description: utils.String("test resp"), diff --git a/integrationtest/user_test.go b/integrationtest/user_test.go index cb284655..76cf40fa 100644 --- a/integrationtest/user_test.go +++ b/integrationtest/user_test.go @@ -4,9 +4,8 @@ import ( "context" "net/http" - apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" - "github.com/jiaozifs/jiaozifs/api" + apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" "github.com/smartystreets/goconvey/convey" ) diff --git a/integrationtest/wip_object_test.go b/integrationtest/wip_object_test.go index 5944da46..8c1f6172 100644 --- a/integrationtest/wip_object_test.go +++ b/integrationtest/wip_object_test.go @@ -4,10 +4,9 @@ import ( "context" "net/http" - "github.com/jiaozifs/jiaozifs/utils" - "github.com/jiaozifs/jiaozifs/api" apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" + "github.com/jiaozifs/jiaozifs/utils" "github.com/smartystreets/goconvey/convey" ) diff --git a/models/branch.go b/models/branch.go index 4735fa47..9dafdf3b 100644 --- a/models/branch.go +++ b/models/branch.go @@ -4,9 +4,8 @@ import ( "context" "time" - "github.com/jiaozifs/jiaozifs/utils/hash" - "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/uptrace/bun" ) diff --git a/models/branch_test.go b/models/branch_test.go index 244219f0..9047ad50 100644 --- a/models/branch_test.go +++ b/models/branch_test.go @@ -4,13 +4,12 @@ import ( "context" "testing" - "github.com/jiaozifs/jiaozifs/utils/hash" - "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" "github.com/google/uuid" "github.com/jiaozifs/jiaozifs/models" "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/stretchr/testify/require" ) @@ -23,25 +22,25 @@ func TestRefRepoInsert(t *testing.T) { branchModel := &models.Branches{} require.NoError(t, gofakeit.Struct(branchModel)) - newBrance, err := repo.Insert(ctx, branchModel) + newBranch, err := repo.Insert(ctx, branchModel) require.NoError(t, err) - require.NotEqual(t, uuid.Nil, newBrance.ID) + require.NotEqual(t, uuid.Nil, newBranch.ID) getBranchParams := models.NewGetBranchParams(). - SetID(newBrance.ID). - SetRepositoryID(newBrance.RepositoryID). - SetName(newBrance.Name) + SetID(newBranch.ID). + SetRepositoryID(newBranch.RepositoryID). + SetName(newBranch.Name) branch, err := repo.Get(ctx, getBranchParams) require.NoError(t, err) require.True(t, cmp.Equal(branchModel, branch, dbTimeCmpOpt)) mockHash := hash.Hash("mock hash") - err = repo.UpdateByID(ctx, models.NewUpdateBranchParams(newBrance.ID).SetCommitHash(mockHash)) + err = repo.UpdateByID(ctx, models.NewUpdateBranchParams(newBranch.ID).SetCommitHash(mockHash)) require.NoError(t, err) branchAfterUpdated, err := repo.Get(ctx, &models.GetBranchParams{ - ID: newBrance.ID, + ID: newBranch.ID, }) require.NoError(t, err) require.Equal(t, mockHash, branchAfterUpdated.CommitHash) diff --git a/models/commit_test.go b/models/commit_test.go index ede2f717..e56619ca 100644 --- a/models/commit_test.go +++ b/models/commit_test.go @@ -4,10 +4,9 @@ import ( "context" "testing" - "github.com/google/uuid" - "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" + "github.com/google/uuid" "github.com/jiaozifs/jiaozifs/models" "github.com/jiaozifs/jiaozifs/testhelper" "github.com/stretchr/testify/require" diff --git a/models/models.go b/models/models.go index e2916363..6042c0f1 100644 --- a/models/models.go +++ b/models/models.go @@ -4,12 +4,11 @@ import ( "context" "database/sql" - "github.com/uptrace/bun/extra/bundebug" - "github.com/jiaozifs/jiaozifs/config" "github.com/uptrace/bun" "github.com/uptrace/bun/dialect/pgdialect" "github.com/uptrace/bun/driver/pgdriver" + "github.com/uptrace/bun/extra/bundebug" "go.uber.org/fx" ) diff --git a/models/models_test.go b/models/models_test.go index c8be9490..0a3313f4 100644 --- a/models/models_test.go +++ b/models/models_test.go @@ -3,18 +3,13 @@ package models_test import ( "context" "fmt" - "testing" - "github.com/jiaozifs/jiaozifs/models" - embeddedpostgres "github.com/fergusstrange/embedded-postgres" - + "github.com/jiaozifs/jiaozifs/config" + "github.com/jiaozifs/jiaozifs/models" "github.com/phayes/freeport" "github.com/stretchr/testify/require" - - "github.com/jiaozifs/jiaozifs/config" - "go.uber.org/fx/fxtest" ) diff --git a/models/repo.go b/models/repo.go index 50ff97e7..385704f0 100644 --- a/models/repo.go +++ b/models/repo.go @@ -5,7 +5,6 @@ import ( "database/sql" "github.com/google/uuid" - "github.com/uptrace/bun" ) diff --git a/models/repo_test.go b/models/repo_test.go index d2970328..57cbf800 100644 --- a/models/repo_test.go +++ b/models/repo_test.go @@ -7,9 +7,8 @@ import ( "fmt" "testing" - "github.com/google/uuid" - "github.com/brianvoe/gofakeit/v6" + "github.com/google/uuid" "github.com/jiaozifs/jiaozifs/models" "github.com/jiaozifs/jiaozifs/testhelper" "github.com/stretchr/testify/require" diff --git a/models/tag.go b/models/tag.go index e219431f..b5a3840a 100644 --- a/models/tag.go +++ b/models/tag.go @@ -5,7 +5,6 @@ import ( "time" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/uptrace/bun" ) diff --git a/models/tag_test.go b/models/tag_test.go index 8ec648eb..b81fc4c6 100644 --- a/models/tag_test.go +++ b/models/tag_test.go @@ -8,7 +8,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/uuid" "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/testhelper" "github.com/stretchr/testify/require" ) diff --git a/models/tree_test.go b/models/tree_test.go index 60cdd960..deb9a303 100644 --- a/models/tree_test.go +++ b/models/tree_test.go @@ -4,13 +4,11 @@ import ( "context" "testing" - "github.com/google/uuid" - - "github.com/jiaozifs/jiaozifs/models/filemode" - "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" + "github.com/google/uuid" "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/models/filemode" "github.com/jiaozifs/jiaozifs/testhelper" "github.com/stretchr/testify/require" ) diff --git a/models/user_test.go b/models/user_test.go index a3aaeed7..335bc2ad 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -5,16 +5,11 @@ import ( "testing" "time" - "github.com/jiaozifs/jiaozifs/testhelper" - "github.com/brianvoe/gofakeit/v6" - "github.com/google/go-cmp/cmp" - "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/models" - + "github.com/jiaozifs/jiaozifs/testhelper" "github.com/stretchr/testify/require" ) diff --git a/models/wip_test.go b/models/wip_test.go index c49455cc..f8dd8640 100644 --- a/models/wip_test.go +++ b/models/wip_test.go @@ -4,13 +4,12 @@ import ( "context" "testing" - "github.com/jiaozifs/jiaozifs/utils/hash" - "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" "github.com/google/uuid" "github.com/jiaozifs/jiaozifs/models" "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/stretchr/testify/require" ) diff --git a/versionmgr/adapter_from_config.go b/versionmgr/adapter_from_config.go new file mode 100644 index 00000000..35e2f5f7 --- /dev/null +++ b/versionmgr/adapter_from_config.go @@ -0,0 +1,23 @@ +package versionmgr + +import ( + "context" + "encoding/json" + + "github.com/jiaozifs/jiaozifs/block" + "github.com/jiaozifs/jiaozifs/block/factory" + "github.com/jiaozifs/jiaozifs/config" +) + +func AdapterFromConfig(ctx context.Context, jsonParams string) (block.Adapter, error) { + var cfg = config.BlockStoreConfig{} + err := json.Unmarshal([]byte(jsonParams), &cfg) + if err != nil { + return nil, err + } + adapter, err := factory.BuildBlockAdapter(ctx, &cfg) + if err != nil { + return nil, err + } + return adapter, err +} diff --git a/versionmgr/adapter_from_config_test.go b/versionmgr/adapter_from_config_test.go new file mode 100644 index 00000000..cded6a1e --- /dev/null +++ b/versionmgr/adapter_from_config_test.go @@ -0,0 +1,30 @@ +package versionmgr + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestAdapterFromConfig(t *testing.T) { + ctx := context.Background() + t.Run("success", func(t *testing.T) { + data := `{"Type":"local","DefaultNamespacePrefix":null,"Local":{"Path":"~/.jiaozifs/blockstore","ImportEnabled":false,"ImportHidden":false,"AllowedExternalPrefixes":null},"S3":null,"Azure":null,"GS":null}` + adapter, err := AdapterFromConfig(ctx, data) + require.NoError(t, err) + require.Equal(t, "local", adapter.BlockstoreType()) + }) + + t.Run("marshal fail", func(t *testing.T) { + data := `{"Type":"local",DefaultNamespacePrefix":null,"Local":{"Path":"~/.jiaozifs/blockstore","ImportEnabled":false,"ImportHidden":false,"AllowedExternalPrefixes":null},"S3":null,"Azure":null,"GS":null}` + _, err := AdapterFromConfig(ctx, data) + require.Error(t, err) + }) + + t.Run("unsupport type", func(t *testing.T) { + data := `{"Type":"mock"}` + _, err := AdapterFromConfig(ctx, data) + require.Error(t, err) + }) +} diff --git a/versionmgr/changes_test.go b/versionmgr/changes_test.go index 5fd69122..5f5520e7 100644 --- a/versionmgr/changes_test.go +++ b/versionmgr/changes_test.go @@ -8,13 +8,10 @@ import ( "strings" "testing" - "github.com/stretchr/testify/require" - - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" - "github.com/jiaozifs/jiaozifs/utils/hash" - + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" + "github.com/stretchr/testify/require" ) var _ noder.Noder = (*MockNode)(nil) diff --git a/versionmgr/commit.go b/versionmgr/commit.go deleted file mode 100644 index caeac473..00000000 --- a/versionmgr/commit.go +++ /dev/null @@ -1,233 +0,0 @@ -package versionmgr - -import ( - "context" - "fmt" - "time" - - "github.com/google/uuid" - logging "github.com/ipfs/go-log/v2" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/utils/hash" -) - -var ( - commitLog = logging.Logger("commit") -) - -// CommitOp used to wrap some function for commit, todo not easy to use, optimize it -type CommitOp struct { - commit *models.Commit - repoID uuid.UUID - - repo models.IRepo -} - -// NewCommitOp create commit operation with repo and exit commit, if operate with new repo, set commit arguments to nil -func NewCommitOp(repo models.IRepo, repoID uuid.UUID, commit *models.Commit) *CommitOp { - return &CommitOp{ - repoID: repoID, - commit: commit, //commit maybe nil - repo: repo, - } -} - -// Commit return commit -func (commitOp *CommitOp) Commit() *models.Commit { - return commitOp.commit -} - -// CommitRepo return commit repo -func (commitOp *CommitOp) CommitRepo() models.ICommitRepo { - return commitOp.repo.CommitRepo(commitOp.repoID) -} - -// FileTreeRepo return file tree repo -func (commitOp *CommitOp) FileTreeRepo() models.IFileTreeRepo { - return commitOp.repo.FileTreeRepo(commitOp.repoID) -} - -// AddCommit append a new commit to current head, read changes from wip, than create a new commit with parent point to current head, -// and replace tree hash with wip's currentTreeHash. -func (commitOp *CommitOp) AddCommit(ctx context.Context, committer *models.User, wipID uuid.UUID, msg string) (*CommitOp, error) { - wip, err := commitOp.repo.WipRepo().Get(ctx, models.NewGetWipParams().SetID(wipID)) - if err != nil { - return nil, err - } - - creator, err := commitOp.repo.UserRepo().Get(ctx, models.NewGetUserParams().SetID(wip.CreatorID)) - if err != nil { - return nil, err - } - - parentHash := []hash.Hash{} - if commitOp.commit != nil { - parentHash = []hash.Hash{commitOp.commit.Hash} - } - commit := &models.Commit{ - Hash: nil, - RepositoryID: commitOp.repoID, - Author: models.Signature{ - Name: creator.Name, - Email: creator.Email, - When: wip.UpdatedAt, - }, - Committer: models.Signature{ - Name: committer.Name, - Email: committer.Email, - When: time.Now(), - }, - MergeTag: "", - Message: msg, - TreeHash: wip.CurrentTree, - ParentHashes: parentHash, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - commitHash, err := commit.GetHash() - if err != nil { - return nil, err - } - commit.Hash = commitHash - _, err = commitOp.CommitRepo().Insert(ctx, commit) - if err != nil { - return nil, err - } - - return NewCommitOp(commitOp.repo, commitOp.repoID, commit), nil -} - -// DiffCommit find file changes in two commit -func (commitOp *CommitOp) DiffCommit(ctx context.Context, toCommitID hash.Hash) (*Changes, error) { - workTree, err := NewWorkTree(ctx, commitOp.FileTreeRepo(), models.NewRootTreeEntry(commitOp.Commit().TreeHash)) - if err != nil { - return nil, err - } - toCommit, err := commitOp.repo.CommitRepo(commitOp.repoID).Commit(ctx, toCommitID) - if err != nil { - return nil, err - } - - return workTree.Diff(ctx, toCommit.TreeHash) -} - -// Merge implement merge like git, docs https://en.wikipedia.org/wiki/Merge_(version_control) -func (commitOp *CommitOp) Merge(ctx context.Context, merger *models.User, toMergeCommitHash hash.Hash, msg string, resolver ConflictResolver) (*models.Commit, error) { - commitRepo := commitOp.CommitRepo() - - toMergeCommit, err := commitOp.CommitRepo().Commit(ctx, toMergeCommitHash) - if err != nil { - return nil, err - } - - //find accesstor - baseCommitNode := NewCommitNode(ctx, commitOp.Commit(), commitRepo) - toMergeCommitNode := NewCommitNode(ctx, toMergeCommit, commitRepo) - - { - //do nothing while merge is ancestor of base - mergeIsAncestorOfBase, err := toMergeCommitNode.IsAncestor(baseCommitNode) - if err != nil { - return nil, err - } - - if mergeIsAncestorOfBase { - commitLog.Warnf("merge commit %s is ancestor of base commit %s", toMergeCommitHash, commitOp.Commit().Hash) - return commitOp.Commit(), nil - } - } - - { - //try fast-forward merge no need to create new commit node - baseIsAncestorOfMerge, err := baseCommitNode.IsAncestor(toMergeCommitNode) - if err != nil { - return nil, err - } - - if baseIsAncestorOfMerge { - commitLog.Warnf("base commit %s is ancestor of merge commit %s", toMergeCommitHash, commitOp.Commit().Hash) - return toMergeCommit, nil - } - } - - // three-way merge - bestAncestor, err := baseCommitNode.MergeBase(toMergeCommitNode) - if err != nil { - return nil, err - } - - if len(bestAncestor) == 0 { - return nil, fmt.Errorf("no common ancesstor find") - } - - bestCommit := bestAncestor[0] - if len(bestAncestor) > 1 { - //merge cross merge create virtual commit - firstCommit := NewCommitOp(commitOp.repo, commitOp.repoID, bestAncestor[0].Commit()) - virtualCommit, err := firstCommit.Merge(ctx, merger, bestAncestor[1].Commit().Hash, "", resolver) - if err != nil { - return nil, err - } - - bestCommit = NewCommitNode(ctx, virtualCommit, commitRepo) - } - - bestCommitOp := NewCommitOp(commitOp.repo, commitOp.repoID, bestAncestor[0].Commit()) - baseDiff, err := bestCommitOp.DiffCommit(ctx, commitOp.Commit().Hash) - if err != nil { - return nil, err - } - - mergeDiff, err := bestCommitOp.DiffCommit(ctx, toMergeCommit.Hash) - if err != nil { - return nil, err - } - - //merge diff - workTree, err := NewWorkTree(ctx, commitOp.FileTreeRepo(), models.NewRootTreeEntry(bestCommit.Commit().TreeHash)) - if err != nil { - return nil, err - } - - cmw := NewChangesMergeIter(baseDiff, mergeDiff, resolver) - for cmw.Has() { - change, err := cmw.Next() - if err != nil { - return nil, err - } - //apply change - err = workTree.ApplyOneChange(ctx, change) - if err != nil { - return nil, err - } - } - - author := models.Signature{ - Name: merger.Name, - Email: merger.Email, - When: time.Now(), - } - - mergeCommit := &models.Commit{ - Author: author, - RepositoryID: commitOp.repoID, - Committer: author, - MergeTag: "", - Message: msg, - TreeHash: workTree.Root().Hash(), - ParentHashes: []hash.Hash{commitOp.commit.Hash, toMergeCommitHash}, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - hash, err := mergeCommit.GetHash() - if err != nil { - return nil, err - } - mergeCommit.Hash = hash - - mergeCommitResult, err := commitRepo.Insert(ctx, mergeCommit) - if err != nil { - return nil, err - } - return mergeCommitResult, nil -} diff --git a/versionmgr/commit_node.go b/versionmgr/commit_node.go index 1bedabc9..f09152c8 100644 --- a/versionmgr/commit_node.go +++ b/versionmgr/commit_node.go @@ -5,7 +5,6 @@ import ( "errors" "io" - "github.com/go-git/go-git/v5/plumbing/storer" "github.com/google/uuid" "github.com/jiaozifs/jiaozifs/models" "github.com/jiaozifs/jiaozifs/utils/hash" @@ -15,71 +14,68 @@ var ( ErrStop = errors.New("stop iter") ) -type CommitNode struct { - ctx context.Context +type WrapCommitNode struct { commit *models.Commit commitRepo models.ICommitRepo } -func NewCommitNode(ctx context.Context, commit *models.Commit, commitRepo models.ICommitRepo) *CommitNode { - return &CommitNode{ctx: ctx, commit: commit, commitRepo: commitRepo} +func NewWrapCommitNode(commitRepo models.ICommitRepo, commit *models.Commit) *WrapCommitNode { + return &WrapCommitNode{commit: commit, commitRepo: commitRepo} } -func (c *CommitNode) Ctx() context.Context { - return c.ctx -} - -func (c *CommitNode) Commit() *models.Commit { +func (c *WrapCommitNode) Commit() *models.Commit { return c.commit } -func (c *CommitNode) RepoID() uuid.UUID { +func (c *WrapCommitNode) RepoID() uuid.UUID { return c.commit.RepositoryID } // TreeHash returns the TreeHash in the commit. -func (c *CommitNode) TreeHash() hash.Hash { +func (c *WrapCommitNode) TreeHash() hash.Hash { return c.commit.TreeHash } +// Hash returns the Hash in the commit. +func (c *WrapCommitNode) Hash() hash.Hash { + return c.commit.Hash +} + // Parents return a CommitIter to the parent Commits. -func (c *CommitNode) Parents() ([]*CommitNode, error) { - parentNodes := make([]*CommitNode, len(c.commit.ParentHashes)) - for _, hash := range c.commit.ParentHashes { - commit, err := c.commitRepo.Commit(c.ctx, hash) +func (c *WrapCommitNode) Parents(ctx context.Context) ([]*WrapCommitNode, error) { + parentNodes := make([]*WrapCommitNode, len(c.commit.ParentHashes)) + for index, hash := range c.commit.ParentHashes { + commit, err := c.commitRepo.Commit(ctx, hash) if err != nil { return nil, err } - parentNodes = append(parentNodes, &CommitNode{ - ctx: c.ctx, + parentNodes[index] = &WrapCommitNode{ commit: commit, commitRepo: c.commitRepo, - }) + } } return parentNodes, nil } -func (c *CommitNode) GetCommit(hash hash.Hash) (*CommitNode, error) { - commit, err := c.commitRepo.Commit(c.ctx, hash) +func (c *WrapCommitNode) GetCommit(ctx context.Context, hash hash.Hash) (*WrapCommitNode, error) { + commit, err := c.commitRepo.Commit(ctx, hash) if err != nil { return nil, err } - return &CommitNode{ - ctx: c.ctx, + return &WrapCommitNode{ commit: commit, commitRepo: c.commitRepo, }, nil } -func (c *CommitNode) GetCommits(hashes []hash.Hash) ([]*CommitNode, error) { - commits := make([]*CommitNode, len(hashes)) +func (c *WrapCommitNode) GetCommits(ctx context.Context, hashes []hash.Hash) ([]*WrapCommitNode, error) { + commits := make([]*WrapCommitNode, len(hashes)) for i, hash := range hashes { - commit, err := c.commitRepo.Commit(c.ctx, hash) + commit, err := c.commitRepo.Commit(ctx, hash) if err != nil { return nil, err } - commits[i] = &CommitNode{ - ctx: c.ctx, + commits[i] = &WrapCommitNode{ commit: commit, commitRepo: c.commitRepo, } @@ -89,25 +85,25 @@ func (c *CommitNode) GetCommits(hashes []hash.Hash) ([]*CommitNode, error) { // CommitIter is a generic closable interface for iterating over commits. type CommitIter interface { - Next() (*CommitNode, error) - ForEach(func(*CommitNode) error) error + Next() (*WrapCommitNode, error) + ForEach(func(*WrapCommitNode) error) error } var _ CommitIter = (*arraryCommitIter)(nil) type arraryCommitIter struct { - commits []*CommitNode + commits []*WrapCommitNode idx int } -func newArrayCommitIter(commits []*CommitNode) *arraryCommitIter { +func newArrayCommitIter(commits []*WrapCommitNode) *arraryCommitIter { return &arraryCommitIter{ commits: commits, idx: -1, } } -func (a *arraryCommitIter) Next() (*CommitNode, error) { +func (a *arraryCommitIter) Next() (*WrapCommitNode, error) { if a.idx < len(a.commits)-1 { a.idx++ return a.commits[a.idx], nil @@ -115,10 +111,10 @@ func (a *arraryCommitIter) Next() (*CommitNode, error) { return nil, io.EOF } -func (a *arraryCommitIter) ForEach(f func(*CommitNode) error) error { +func (a *arraryCommitIter) ForEach(f func(*WrapCommitNode) error) error { for _, commit := range a.commits { err := f(commit) - if errors.Is(err, storer.ErrStop) { + if errors.Is(err, ErrStop) { break } if err != nil { @@ -129,5 +125,5 @@ func (a *arraryCommitIter) ForEach(f func(*CommitNode) error) error { } func (a *arraryCommitIter) Has() bool { - return a.idx == len(a.commits)-1 + return a.idx < len(a.commits)-1 } diff --git a/versionmgr/commit_node_test.go b/versionmgr/commit_node_test.go new file mode 100644 index 00000000..fdbe6c69 --- /dev/null +++ b/versionmgr/commit_node_test.go @@ -0,0 +1,120 @@ +package versionmgr + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/utils/hash" + + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/testhelper" +) + +// TestWrapCommitNode +// +// A--->B +// / \ +// root E-->F +// \ / +// C---->D +func TestWrapCommitNode(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + repoID := uuid.New() + repo := models.NewRepo(db) + rootCommit, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "root") + require.NoError(t, err) + commitA, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit a", rootCommit.Hash) + require.NoError(t, err) + + commitB, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commt b", commitA.Hash) + require.NoError(t, err) + + commitC, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit c", rootCommit.Hash) + require.NoError(t, err) + commitD, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit d", commitC.Hash) + require.NoError(t, err) + + commitE, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit e", commitB.Hash, commitD.Hash) + require.NoError(t, err) + + commitF, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit f", commitE.Hash) + require.NoError(t, err) + + commitFNode := NewWrapCommitNode(repo.CommitRepo(repoID), commitF) + + require.Equal(t, commitF.Hash.Hex(), commitFNode.Commit().Hash.Hex()) + require.Equal(t, repoID, commitFNode.RepoID()) + require.Equal(t, hash.EmptyHash, commitFNode.TreeHash()) + require.Equal(t, commitF.Hash.Hex(), commitFNode.Hash().Hex()) + + t.Run("parent", func(t *testing.T) { + commitENode := NewWrapCommitNode(repo.CommitRepo(repoID), commitE) + node, err := commitENode.Parents(ctx) + require.NoError(t, err) + require.Equal(t, commitB.Hash.Hex(), node[0].Hash().Hex()) + require.Equal(t, commitD.Hash.Hex(), node[1].Hash().Hex()) + }) + t.Run("get commit", func(t *testing.T) { + commit, err := commitFNode.GetCommit(ctx, commitA.Hash) + require.NoError(t, err) + require.Equal(t, commitA.Hash.Hex(), commit.Hash().Hex()) + }) + + t.Run("get commits", func(t *testing.T) { + commits, err := commitFNode.GetCommits(ctx, []hash.Hash{commitA.Hash, commitB.Hash}) + require.NoError(t, err) + require.Equal(t, commitA.Hash.Hex(), commits[0].Hash().Hex()) + require.Equal(t, commitB.Hash.Hex(), commits[1].Hash().Hex()) + }) + + t.Run("get commits", func(t *testing.T) { + commits, err := commitFNode.GetCommits(ctx, []hash.Hash{commitA.Hash, commitB.Hash}) + require.NoError(t, err) + require.Equal(t, commitA.Hash.Hex(), commits[0].Hash().Hex()) + require.Equal(t, commitB.Hash.Hex(), commits[1].Hash().Hex()) + }) + commits, err := commitFNode.GetCommits(ctx, []hash.Hash{commitA.Hash, commitB.Hash}) + require.NoError(t, err) + require.Equal(t, commitA.Hash.Hex(), commits[0].Hash().Hex()) + require.Equal(t, commitB.Hash.Hex(), commits[1].Hash().Hex()) + + t.Run("iter foreach", func(t *testing.T) { + iter := newArrayCommitIter(commits) + var exactCommits []*WrapCommitNode + err := iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + return nil + }) + require.NoError(t, err) + require.Len(t, exactCommits, 2) + require.Equal(t, commitA.Hash.Hex(), exactCommits[0].Hash().Hex()) + require.Equal(t, commitB.Hash.Hex(), exactCommits[1].Hash().Hex()) + }) + + t.Run("hash next", func(t *testing.T) { + iter := newArrayCommitIter(commits) + var exactCommits []*WrapCommitNode + for iter.Has() { + node, err := iter.Next() + require.NoError(t, err) + exactCommits = append(exactCommits, node) + } + require.Len(t, exactCommits, 2) + require.Equal(t, commitA.Hash.Hex(), exactCommits[0].Hash().Hex()) + require.Equal(t, commitB.Hash.Hex(), exactCommits[1].Hash().Hex()) + assertHash(t, exactCommits, commitA.Hash, commitB.Hash) + }) +} + +func assertHash(t *testing.T, exactCommits []*WrapCommitNode, seq ...hash.Hash) { + require.Equal(t, len(exactCommits), len(seq)) + for index, c := range exactCommits { + require.Equal(t, c.Hash().Hex(), seq[index].Hex()) + } +} diff --git a/versionmgr/commit_test.go b/versionmgr/commit_test.go deleted file mode 100644 index 2328ba9e..00000000 --- a/versionmgr/commit_test.go +++ /dev/null @@ -1,441 +0,0 @@ -package versionmgr - -import ( - "context" - "strings" - "testing" - "time" - - "github.com/jiaozifs/jiaozifs/models/filemode" - - "github.com/jiaozifs/jiaozifs/utils" - - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" - - "github.com/google/uuid" - - "github.com/stretchr/testify/require" - - "github.com/jiaozifs/jiaozifs/utils/hash" - - "github.com/jiaozifs/jiaozifs/models" - - "github.com/jiaozifs/jiaozifs/testhelper" -) - -func TestCommitOpDiffCommit(t *testing.T) { - ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint - - repo := models.NewRepo(db) - - user, err := makeUser(ctx, repo.UserRepo(), "admin") - require.NoError(t, err) - - project, err := makeRepository(ctx, repo.RepositoryRepo(), "testproject") - require.NoError(t, err) - - //base branch - baseBranch, err := makeBranch(ctx, repo.BranchRepo(), "feat/base", project.ID, hash.Hash("a")) - require.NoError(t, err) - - //commit1 a.txt b/c.txt b/e.txt - //commit2 a.txt b/d.txt b/e.txt - testData1 := ` -1|a.txt |a -1|b/c.txt |c -1|b/e.txt |e1 -` - - root1, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), EmptyDirEntry, testData1) - require.NoError(t, err) - baseWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseBranch.ID, EmptyRoot.Hash, root1.Hash) - require.NoError(t, err) - - baseCommit, err := NewCommitOp(repo, project.ID, nil).AddCommit(ctx, user, baseWip.ID, "base commit") - require.NoError(t, err) - require.NoError(t, rmWip(ctx, repo.WipRepo(), baseWip.ID)) - - testData2 := ` -3|a.txt |a1 -2|b/c.txt |d -3|b/e.txt |e2 -1|b/g.txt |g1 -` - root2, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(root1.Hash), testData2) - require.NoError(t, err) - - secondWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseBranch.ID, EmptyRoot.Hash, root2.Hash) - require.NoError(t, err) - secondCommit, err := NewCommitOp(repo, project.ID, nil).AddCommit(ctx, user, secondWip.ID, "merge commit") - require.NoError(t, err) - require.NoError(t, rmWip(ctx, repo.WipRepo(), secondWip.ID)) - - changes, err := baseCommit.DiffCommit(ctx, secondCommit.Commit().Hash) - require.NoError(t, err) - require.Equal(t, 4, changes.Num()) - require.Equal(t, "a.txt", changes.Index(0).Path()) - action, err := changes.Index(0).Action() - require.NoError(t, err) - require.Equal(t, merkletrie.Modify, action) - - require.Equal(t, "b/c.txt", changes.Index(1).Path()) - action, err = changes.Index(1).Action() - require.NoError(t, err) - require.Equal(t, merkletrie.Delete, action) - require.Equal(t, "b/e.txt", changes.Index(2).Path()) - action, err = changes.Index(2).Action() - require.NoError(t, err) - require.Equal(t, merkletrie.Modify, action) - - require.Equal(t, "b/g.txt", changes.Index(3).Path()) - action, err = changes.Index(3).Action() - require.NoError(t, err) - require.Equal(t, merkletrie.Insert, action) -} - -// TestCommitOpMerge -//example -// A -----C -// | | \ -// | | \ -// / \ F \ -// / \ / \ -// root AB CG -// \ / \ / -// \ / \ / -// B-D-E--- G - -func TestCommitOpMerge(t *testing.T) { - ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint - - repo := models.NewRepo(db) - - user, err := makeUser(ctx, repo.UserRepo(), "admin") - require.NoError(t, err) - - project, err := makeRepository(ctx, repo.RepositoryRepo(), "testproject") - require.NoError(t, err) - - testData := ` -1|a.txt |h1 -1|b/c.txt |h2 -` - oriRoot, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), EmptyDirEntry, testData) - require.NoError(t, err) - //base branch - baseBranch, err := makeBranch(ctx, repo.BranchRepo(), "feat/base", project.ID, hash.Hash("a")) - require.NoError(t, err) - - oriWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseBranch.ID, hash.Hash{}, oriRoot.Hash) - require.NoError(t, err) - - oriCommit, err := NewCommitOp(repo, project.ID, nil).AddCommit(ctx, user, oriWip.ID, "") - require.NoError(t, err) - require.NoError(t, rmWip(ctx, repo.WipRepo(), oriWip.ID)) - //modify a.txt - //CommitA - testData = ` -3|a.txt |h5 -3|b/c.txt |h2 -` - baseModify, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(oriRoot.Hash), testData) - require.NoError(t, err) - - baseWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseBranch.ID, oriRoot.Hash, baseModify.Hash) - require.NoError(t, err) - - commitA, err := NewCommitOp(repo, project.ID, oriCommit.Commit()).AddCommit(ctx, user, baseWip.ID, "commit a") - require.NoError(t, err) - require.NoError(t, rmWip(ctx, repo.WipRepo(), baseWip.ID)) - - //toMerge branch - mergeBranch, err := makeBranch(ctx, repo.BranchRepo(), "feat/merge", project.ID, hash.Hash("a")) - require.NoError(t, err) - - //modify a.txt - //CommitB - testData = ` -3|a.txt |h4 -3|b/c.txt |h2 -` - mergeModify, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(oriRoot.Hash), testData) - require.NoError(t, err) - mergeWip, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeBranch.ID, oriRoot.Hash, mergeModify.Hash) - require.NoError(t, err) - commitB, err := NewCommitOp(repo, project.ID, oriCommit.Commit()).AddCommit(ctx, user, mergeWip.ID, "commit b") - require.NoError(t, err) - require.NoError(t, rmWip(ctx, repo.WipRepo(), mergeWip.ID)) - - //CommitAB - commitAB, err := commitA.Merge(ctx, user, commitB.Commit().Hash, "commit ab", LeastHashResolve) - require.NoError(t, err) - - //CommitAS - testData = ` -1|x.txt |h4 -` - rootF, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(commitAB.TreeHash), testData) - require.NoError(t, err) - mergeWipF, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeBranch.ID, commitAB.TreeHash, rootF.Hash) - require.NoError(t, err) - commitF, err := NewCommitOp(repo, project.ID, oriCommit.Commit()).AddCommit(ctx, user, mergeWipF.ID, "commit f") - require.NoError(t, err) - require.NoError(t, rmWip(ctx, repo.WipRepo(), mergeWipF.ID)) - - //commitC - commitC, err := commitA.Merge(ctx, user, commitF.Commit().Hash, "commit c", LeastHashResolve) - require.NoError(t, err) - - //commitD - testData = ` -3|a.txt |h5 -3|b/c.txt |h6 -1|g/c.txt |h7 -` - modifyD, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(commitB.Commit().TreeHash), testData) - require.NoError(t, err) - mergeWipD, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeBranch.ID, commitB.Commit().Hash, modifyD.Hash) - require.NoError(t, err) - commitD, err := commitB.AddCommit(ctx, user, mergeWipD.ID, "commit d") - require.NoError(t, err) - require.NoError(t, rmWip(ctx, repo.WipRepo(), mergeWipD.ID)) - //commitE - testData = ` -2|a.txt |h4 -` - modifyE, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(commitD.Commit().TreeHash), testData) - require.NoError(t, err) - mergeWipE, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeBranch.ID, commitD.Commit().Hash, modifyE.Hash) - require.NoError(t, err) - commitE, err := commitD.AddCommit(ctx, user, mergeWipE.ID, "commit e") - require.NoError(t, err) - require.NoError(t, rmWip(ctx, repo.WipRepo(), mergeWipE.ID)) - //test fast-ward - - fastMergeCommit, err := commitB.Merge(ctx, user, commitE.Commit().Hash, "", LeastHashResolve) - require.NoError(t, err) - require.Equal(t, commitE.Commit().Hash.Hex(), fastMergeCommit.Hash.Hex()) - - //commitG - commitG, err := commitE.Merge(ctx, user, commitAB.Hash, "commit c", LeastHashResolve) - require.NoError(t, err) - - _, err = NewCommitOp(repo, project.ID, commitC).Merge(ctx, user, commitG.Hash, "commit c", LeastHashResolve) - require.NoError(t, err) -} - -// TestCrissCrossMerge -// -// example -// -// C--------D -// / \ / \ -// / \ / \ -// root * CG -// \ / \ / -// \ / \ / -// B-------G -func TestCrissCrossMerge(t *testing.T) { - ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint - - repo := models.NewRepo(db) - - user, err := makeUser(ctx, repo.UserRepo(), "admin") - require.NoError(t, err) - - project, err := makeRepository(ctx, repo.RepositoryRepo(), "testproject") - require.NoError(t, err) - - testData := ` -1|a.txt |h1 -1|b.txt |h2 -` - oriRoot, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), EmptyDirEntry, testData) - require.NoError(t, err) - //base branch - baseBranch, err := makeBranch(ctx, repo.BranchRepo(), "feat/base", project.ID, hash.Hash("a")) - require.NoError(t, err) - - oriWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseBranch.ID, hash.Hash{}, oriRoot.Hash) - require.NoError(t, err) - - oriCommit, err := NewCommitOp(repo, project.ID, nil).AddCommit(ctx, user, oriWip.ID, "") - require.NoError(t, err) - require.NoError(t, rmWip(ctx, repo.WipRepo(), oriWip.ID)) - - //CommitA - testData = ` -3|a.txt |h1 -3|b.txt |h3 -` - baseModify, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(oriRoot.Hash), testData) - require.NoError(t, err) - - baseWip, err := makeWip(ctx, repo.WipRepo(), project.ID, baseBranch.ID, oriRoot.Hash, baseModify.Hash) - require.NoError(t, err) - - commitA, err := NewCommitOp(repo, project.ID, oriCommit.Commit()).AddCommit(ctx, user, baseWip.ID, "commit a") - require.NoError(t, err) - require.NoError(t, rmWip(ctx, repo.WipRepo(), baseWip.ID)) - - //toMerge branch - mergeBranch, err := makeBranch(ctx, repo.BranchRepo(), "feat/merge", project.ID, hash.Hash("a")) - require.NoError(t, err) - - //modify a.txt - //CommitB - testData = ` -3|a.txt |h4 -3|b.txt |h2 -` - mergeModify, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(oriRoot.Hash), testData) - require.NoError(t, err) - mergeWip, err := makeWip(ctx, repo.WipRepo(), project.ID, mergeBranch.ID, oriRoot.Hash, mergeModify.Hash) - require.NoError(t, err) - commitB, err := NewCommitOp(repo, project.ID, oriCommit.Commit()).AddCommit(ctx, user, mergeWip.ID, "commit b") - require.NoError(t, err) - require.NoError(t, rmWip(ctx, repo.WipRepo(), mergeWip.ID)) - - commitAB, err := commitA.Merge(ctx, user, commitB.Commit().Hash, "commit ab", LeastHashResolve) - require.NoError(t, err) - - commitBA, err := commitB.Merge(ctx, user, commitA.Commit().Hash, "commit ba", LeastHashResolve) - require.NoError(t, err) - - _, err = NewCommitOp(repo, project.ID, commitAB).Merge(ctx, user, commitBA.Hash, "cross commit", LeastHashResolve) - require.NoError(t, err) -} - -func makeUser(ctx context.Context, userRepo models.IUserRepo, name string) (*models.User, error) { - user := &models.User{ - Name: name, - Email: "xxx@gg.com", - EncryptedPassword: "123", - CurrentSignInAt: time.Time{}, - LastSignInAt: time.Time{}, - CurrentSignInIP: "", - LastSignInIP: "", - CreatedAt: time.Time{}, - UpdatedAt: time.Time{}, - } - return userRepo.Insert(ctx, user) -} - -func makeRepository(ctx context.Context, repoRepo models.IRepositoryRepo, name string) (*models.Repository, error) { - user := &models.Repository{ - Name: name, - Description: utils.String("test"), - HEAD: "main", - CreatorID: uuid.UUID{}, - CreatedAt: time.Time{}, - UpdatedAt: time.Time{}, - } - return repoRepo.Insert(ctx, user) -} - -// nolint -func makeCommit(ctx context.Context, commitRepo models.ICommitRepo, treeHash hash.Hash, msg string, parentsHash ...hash.Hash) (*models.Commit, error) { - commit := &models.Commit{ - Hash: hash.Hash("mock"), - Author: models.Signature{ - Name: "admin", - Email: "xxx@gg.com", - When: time.Time{}, - }, - Committer: models.Signature{ - Name: "admin", - Email: "xxx@gg.com", - When: time.Time{}, - }, - TreeHash: treeHash, - ParentHashes: parentsHash, - Message: msg, - } - obj, err := commitRepo.Insert(ctx, commit) - if err != nil { - return nil, err - } - return obj, nil -} - -func makeBranch(ctx context.Context, branchRepo models.IBranchRepo, name string, repoID uuid.UUID, commitHash hash.Hash) (*models.Branches, error) { - branch := &models.Branches{ - RepositoryID: repoID, - CommitHash: commitHash, - Name: name, - Description: utils.String("test"), - CreatorID: uuid.UUID{}, - CreatedAt: time.Time{}, - UpdatedAt: time.Time{}, - } - return branchRepo.Insert(ctx, branch) -} - -func makeWip(ctx context.Context, wipRepo models.IWipRepo, repoID, branchID uuid.UUID, parentHash, curHash hash.Hash) (*models.WorkingInProcess, error) { - wip := &models.WorkingInProcess{ - CurrentTree: curHash, - BaseCommit: parentHash, - RefID: branchID, - RepositoryID: repoID, - CreatorID: uuid.UUID{}, - CreatedAt: time.Time{}, - UpdatedAt: time.Time{}, - } - return wipRepo.Insert(ctx, wip) -} - -func rmWip(ctx context.Context, wipRepo models.IWipRepo, wipID uuid.UUID) error { - _, err := wipRepo.Delete(ctx, models.NewDeleteWipParams().SetID(wipID)) - return err -} - -func makeRoot(ctx context.Context, objRepo models.IFileTreeRepo, treeEntry models.TreeEntry, testData string) (*models.TreeNode, error) { - lines := strings.Split(testData, "\n") - treeOp, err := NewWorkTree(ctx, objRepo, treeEntry) - if err != nil { - return nil, err - } - for _, line := range lines { - if len(strings.TrimSpace(line)) == 0 { - continue - } - commitData := strings.Split(strings.TrimSpace(line), "|") - fullPath := strings.TrimSpace(commitData[1]) - fileHash := strings.TrimSpace(commitData[2]) - blob := &models.Blob{ - Hash: hash.Hash(fileHash), - RepositoryID: objRepo.RepositoryID(), - Type: models.BlobObject, - Size: 10, - Properties: models.Property{Mode: filemode.Regular}, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - - if commitData[0] == "1" { - err = treeOp.AddLeaf(ctx, fullPath, blob) - if err != nil { - return nil, err - } - } else if commitData[0] == "3" { - err = treeOp.ReplaceLeaf(ctx, fullPath, blob) - if err != nil { - return nil, err - } - } else { - //2 - err = treeOp.RemoveEntry(ctx, fullPath) - if err != nil { - return nil, err - } - } - - } - return treeOp.Root().TreeNode(), nil -} diff --git a/versionmgr/commit_walker.go b/versionmgr/commit_walker.go index 000aea44..f4b0054f 100644 --- a/versionmgr/commit_walker.go +++ b/versionmgr/commit_walker.go @@ -1,19 +1,19 @@ package versionmgr import ( + "context" "errors" "io" "github.com/jiaozifs/jiaozifs/utils/hash" - - "github.com/go-git/go-git/v5/plumbing/storer" ) type commitPreIterator struct { + ctx context.Context seenExternal map[string]bool seen map[string]bool stack []CommitIter - start *CommitNode + start *WrapCommitNode } // NewCommitPreorderIter returns a CommitIter that walks the commit history, @@ -24,7 +24,8 @@ type commitPreIterator struct { // cannot be traversed (e.g. missing objects). Ignore allows to skip some // commits from being iterated. func NewCommitPreorderIter( - c *CommitNode, + ctx context.Context, + c *WrapCommitNode, seenExternal map[string]bool, ignore []hash.Hash, ) CommitIter { @@ -34,6 +35,7 @@ func NewCommitPreorderIter( } return &commitPreIterator{ + ctx: ctx, seenExternal: seenExternal, seen: seen, stack: make([]CommitIter, 0), @@ -41,8 +43,8 @@ func NewCommitPreorderIter( } } -func (w *commitPreIterator) Next() (*CommitNode, error) { - var c *CommitNode +func (w *commitPreIterator) Next() (*WrapCommitNode, error) { + var c *WrapCommitNode for { if w.start != nil { c = w.start @@ -72,7 +74,7 @@ func (w *commitPreIterator) Next() (*CommitNode, error) { w.seen[c.Commit().Hash.Hex()] = true if c.Commit().NumParents() > 0 { - commitIter, err := filteredParentIter(c, w.seen) + commitIter, err := filteredParentIter(w.ctx, c, w.seen) if err != nil { return nil, err } @@ -83,14 +85,14 @@ func (w *commitPreIterator) Next() (*CommitNode, error) { } } -func filteredParentIter(c *CommitNode, seen map[string]bool) (CommitIter, error) { +func filteredParentIter(ctx context.Context, c *WrapCommitNode, seen map[string]bool) (CommitIter, error) { var hashes []hash.Hash for _, h := range c.Commit().ParentHashes { if !seen[h.Hex()] { hashes = append(hashes, h) } } - commits, err := c.GetCommits(hashes) + commits, err := c.GetCommits(ctx, hashes) if err != nil { return nil, err } @@ -98,7 +100,7 @@ func filteredParentIter(c *CommitNode, seen map[string]bool) (CommitIter, error) return newArrayCommitIter(commits), nil } -func (w *commitPreIterator) ForEach(cb func(*CommitNode) error) error { +func (w *commitPreIterator) ForEach(cb func(*WrapCommitNode) error) error { for { c, err := w.Next() if err == io.EOF { @@ -121,7 +123,8 @@ func (w *commitPreIterator) ForEach(cb func(*CommitNode) error) error { } type commitPostIterator struct { - stack []*CommitNode + ctx context.Context + stack []*WrapCommitNode seen map[string]bool } @@ -130,19 +133,20 @@ type commitPostIterator struct { // walking a merge commit, the merged commit will be walked before the base // it was merged on. This can be useful if you wish to see the history in // chronological order. Ignore allows to skip some commits from being iterated. -func NewCommitPostorderIter(c *CommitNode, ignore []hash.Hash) CommitIter { +func NewCommitPostorderIter(ctx context.Context, c *WrapCommitNode, ignore []hash.Hash) CommitIter { seen := make(map[string]bool) for _, h := range ignore { seen[h.Hex()] = true } return &commitPostIterator{ - stack: []*CommitNode{c}, + ctx: ctx, + stack: []*WrapCommitNode{c}, seen: seen, } } -func (w *commitPostIterator) Next() (*CommitNode, error) { +func (w *commitPostIterator) Next() (*WrapCommitNode, error) { for { if len(w.stack) == 0 { return nil, io.EOF @@ -157,18 +161,18 @@ func (w *commitPostIterator) Next() (*CommitNode, error) { w.seen[c.Commit().Hash.Hex()] = true - parentCommits, err := c.Parents() + parentCommits, err := c.Parents(w.ctx) if err != nil { return nil, err } - return c, newArrayCommitIter(parentCommits).ForEach(func(p *CommitNode) error { + return c, newArrayCommitIter(parentCommits).ForEach(func(p *WrapCommitNode) error { w.stack = append(w.stack, p) return nil }) } } -func (w *commitPostIterator) ForEach(cb func(*CommitNode) error) error { +func (w *commitPostIterator) ForEach(cb func(*WrapCommitNode) error) error { for { c, err := w.Next() if err == io.EOF { @@ -179,7 +183,7 @@ func (w *commitPostIterator) ForEach(cb func(*CommitNode) error) error { } err = cb(c) - if errors.Is(err, storer.ErrStop) { + if errors.Is(err, ErrStop) { break } if err != nil { diff --git a/versionmgr/commit_walker_bfs.go b/versionmgr/commit_walker_bfs.go index d83e33bc..f3727414 100644 --- a/versionmgr/commit_walker_bfs.go +++ b/versionmgr/commit_walker_bfs.go @@ -1,17 +1,18 @@ package versionmgr import ( + "context" "errors" "io" - "github.com/go-git/go-git/v5/plumbing/storer" "github.com/jiaozifs/jiaozifs/utils/hash" ) type bfsCommitIterator struct { + ctx context.Context seenExternal map[string]bool seen map[string]bool - queue []*CommitNode + queue []*WrapCommitNode } // NewCommitIterBSF returns a CommitIter that walks the commit history, @@ -22,7 +23,8 @@ type bfsCommitIterator struct { // cannot be traversed (e.g. missing objects). Ignore allows to skip some // commits from being iterated. func NewCommitIterBSF( - c *CommitNode, + ctx context.Context, + c *WrapCommitNode, seenExternal map[string]bool, ignore []hash.Hash, ) CommitIter { @@ -32,17 +34,18 @@ func NewCommitIterBSF( } return &bfsCommitIterator{ + ctx: ctx, seenExternal: seenExternal, seen: seen, - queue: []*CommitNode{c}, + queue: []*WrapCommitNode{c}, } } -func (w *bfsCommitIterator) appendHash(store *CommitNode, h hash.Hash) error { +func (w *bfsCommitIterator) appendHash(ctx context.Context, store *WrapCommitNode, h hash.Hash) error { if w.seen[h.Hex()] || w.seenExternal[h.Hex()] { return nil } - c, err := store.GetCommit(h) + c, err := store.GetCommit(ctx, h) if err != nil { return err } @@ -50,8 +53,8 @@ func (w *bfsCommitIterator) appendHash(store *CommitNode, h hash.Hash) error { return nil } -func (w *bfsCommitIterator) Next() (*CommitNode, error) { - var c *CommitNode +func (w *bfsCommitIterator) Next() (*WrapCommitNode, error) { + var c *WrapCommitNode for { if len(w.queue) == 0 { return nil, io.EOF @@ -66,7 +69,7 @@ func (w *bfsCommitIterator) Next() (*CommitNode, error) { w.seen[c.Commit().Hash.Hex()] = true for _, h := range c.Commit().ParentHashes { - err := w.appendHash(c, h) + err := w.appendHash(w.ctx, c, h) if err != nil { return nil, err } @@ -76,7 +79,7 @@ func (w *bfsCommitIterator) Next() (*CommitNode, error) { } } -func (w *bfsCommitIterator) ForEach(cb func(node *CommitNode) error) error { +func (w *bfsCommitIterator) ForEach(cb func(node *WrapCommitNode) error) error { for { c, err := w.Next() if err == io.EOF { @@ -87,7 +90,7 @@ func (w *bfsCommitIterator) ForEach(cb func(node *CommitNode) error) error { } err = cb(c) - if errors.Is(err, storer.ErrStop) { + if errors.Is(err, ErrStop) { break } if err != nil { diff --git a/versionmgr/commit_walker_bfs_filtered.go b/versionmgr/commit_walker_bfs_filtered.go index a30c76ef..d185d5a0 100644 --- a/versionmgr/commit_walker_bfs_filtered.go +++ b/versionmgr/commit_walker_bfs_filtered.go @@ -1,10 +1,10 @@ package versionmgr import ( + "context" "errors" "io" - "github.com/go-git/go-git/v5/plumbing/storer" "github.com/jiaozifs/jiaozifs/utils/hash" ) @@ -18,13 +18,14 @@ import ( // If no isValid is passed, all ancestors of from commit will be valid. // If no isLimit is limit, all ancestors of all commits will be visited. func NewFilterCommitIter( - from *CommitNode, + ctx context.Context, + from *WrapCommitNode, isValid *CommitFilter, isLimit *CommitFilter, ) CommitIter { var validFilter CommitFilter if isValid == nil { - validFilter = func(_ *CommitNode) bool { + validFilter = func(_ *WrapCommitNode) bool { return true } } else { @@ -33,7 +34,7 @@ func NewFilterCommitIter( var limitFilter CommitFilter if isLimit == nil { - limitFilter = func(_ *CommitNode) bool { + limitFilter = func(_ *WrapCommitNode) bool { return false } } else { @@ -41,30 +42,32 @@ func NewFilterCommitIter( } return &filterCommitIter{ + ctx: ctx, isValid: validFilter, isLimit: limitFilter, visited: map[string]struct{}{}, - queue: []*CommitNode{from}, + queue: []*WrapCommitNode{from}, } } // CommitFilter returns a boolean for the passed Commit -type CommitFilter func(*CommitNode) bool +type CommitFilter func(*WrapCommitNode) bool // filterCommitIter implements CommitIter type filterCommitIter struct { + ctx context.Context isValid CommitFilter isLimit CommitFilter visited map[string]struct{} - queue []*CommitNode + queue []*WrapCommitNode lastErr error } // Next returns the next commit of the CommitIter. // It will return io.EOF if there are no more commits to visit, // or an error if the history could not be traversed. -func (w *filterCommitIter) Next() (*CommitNode, error) { - var commit *CommitNode +func (w *filterCommitIter) Next() (*WrapCommitNode, error) { + var commit *WrapCommitNode var err error for { commit, err = w.popNewFromQueue() @@ -75,7 +78,7 @@ func (w *filterCommitIter) Next() (*CommitNode, error) { w.visited[commit.Commit().Hash.Hex()] = struct{}{} if !w.isLimit(commit) { - err = w.addToQueue(commit, commit.Commit().ParentHashes...) + err = w.addToQueue(w.ctx, commit, commit.Commit().ParentHashes...) if err != nil { return nil, w.close(err) } @@ -89,7 +92,7 @@ func (w *filterCommitIter) Next() (*CommitNode, error) { // ForEach runs the passed callback over each Commit returned by the CommitIter // until the callback returns an error or there is no more commits to traverse. -func (w *filterCommitIter) ForEach(cb func(*CommitNode) error) error { +func (w *filterCommitIter) ForEach(cb func(*WrapCommitNode) error) error { for { commit, err := w.Next() if err == io.EOF { @@ -101,7 +104,7 @@ func (w *filterCommitIter) ForEach(cb func(*CommitNode) error) error { } err = cb(commit) - if errors.Is(err, storer.ErrStop) { + if errors.Is(err, ErrStop) { break } @@ -121,7 +124,7 @@ func (w *filterCommitIter) Error() error { // Close closes the CommitIter func (w *filterCommitIter) Close() { w.visited = map[string]struct{}{} - w.queue = []*CommitNode{} + w.queue = []*WrapCommitNode{} w.isLimit = nil w.isValid = nil } @@ -135,8 +138,8 @@ func (w *filterCommitIter) close(err error) error { // popNewFromQueue returns the first new commit from the internal fifo queue, // or an io.EOF error if the queue is empty -func (w *filterCommitIter) popNewFromQueue() (*CommitNode, error) { - var first *CommitNode +func (w *filterCommitIter) popNewFromQueue() (*WrapCommitNode, error) { + var first *WrapCommitNode for { if len(w.queue) == 0 { if w.lastErr != nil { @@ -159,7 +162,8 @@ func (w *filterCommitIter) popNewFromQueue() (*CommitNode, error) { // addToQueue adds the passed commits to the internal fifo queue if they weren't seen // or returns an error if the passed hashes could not be used to get valid commits func (w *filterCommitIter) addToQueue( - c *CommitNode, + ctx context.Context, + c *WrapCommitNode, hashes ...hash.Hash, ) error { for _, hash := range hashes { @@ -167,7 +171,7 @@ func (w *filterCommitIter) addToQueue( continue } - commit, err := c.GetCommit(hash) + commit, err := c.GetCommit(ctx, hash) if err != nil { return err } diff --git a/versionmgr/commit_walker_bfs_filtered_test.go b/versionmgr/commit_walker_bfs_filtered_test.go new file mode 100644 index 00000000..757d1592 --- /dev/null +++ b/versionmgr/commit_walker_bfs_filtered_test.go @@ -0,0 +1,115 @@ +package versionmgr + +import ( + "bytes" + "context" + "errors" + "testing" + + "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/stretchr/testify/require" +) + +func TestNewFilterCommitIter(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + repoID := uuid.New() + repo := models.NewRepo(db) + rootCommit, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "root") + require.NoError(t, err) + commitA, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit a", rootCommit.Hash) + require.NoError(t, err) + + commitB, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commt b", commitA.Hash) + require.NoError(t, err) + + commitC, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit c", rootCommit.Hash) + require.NoError(t, err) + commitD, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit d", commitC.Hash) + require.NoError(t, err) + + commitE, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit e", commitB.Hash, commitD.Hash) + require.NoError(t, err) + + commitF, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit f", commitE.Hash) + require.NoError(t, err) + + commitFNode := NewWrapCommitNode(repo.CommitRepo(repoID), commitF) + + t.Run("NewFilterCommitIter", func(t *testing.T) { + iter := NewFilterCommitIter(ctx, commitFNode, nil, nil) + var exactCommits []*WrapCommitNode + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + return nil + }) + require.NoError(t, err) + require.Len(t, exactCommits, 7) + assertHash(t, exactCommits, commitF.Hash, commitE.Hash, commitB.Hash, commitD.Hash, commitA.Hash, commitC.Hash, rootCommit.Hash) + }) + + t.Run("NewFilterCommitIter valid", func(t *testing.T) { + valid := func(node *WrapCommitNode) bool { + return !bytes.Equal(node.Hash(), commitB.Hash) + } + iter := NewFilterCommitIter(ctx, commitFNode, (*CommitFilter)(&valid), nil) + var exactCommits []*WrapCommitNode + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + return nil + }) + require.NoError(t, err) + require.Len(t, exactCommits, 6) + assertHash(t, exactCommits, commitF.Hash, commitE.Hash, commitD.Hash, commitA.Hash, commitC.Hash, rootCommit.Hash) + }) + t.Run("NewFilterCommitIter valid", func(t *testing.T) { + isLimit := func(node *WrapCommitNode) bool { + return bytes.Equal(node.Hash(), commitB.Hash) + } + iter := NewFilterCommitIter(ctx, commitFNode, nil, (*CommitFilter)(&isLimit)) + var exactCommits []*WrapCommitNode + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + return nil + }) + require.NoError(t, err) + require.Len(t, exactCommits, 6) + assertHash(t, exactCommits, commitF.Hash, commitE.Hash, commitB.Hash, commitD.Hash, commitC.Hash, rootCommit.Hash) + }) + + t.Run("NewFilterCommitIter ErrStop", func(t *testing.T) { + iter := NewFilterCommitIter(ctx, commitFNode, nil, nil) + var exactCommits []*WrapCommitNode + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + if bytes.Equal(node.Hash(), commitF.Hash) { + return ErrStop + } + return nil + }) + require.NoError(t, err) + require.Len(t, exactCommits, 1) + assertHash(t, exactCommits, commitF.Hash) + }) + + t.Run("NewFilterCommitIter err", func(t *testing.T) { + iter := NewFilterCommitIter(ctx, commitFNode, nil, nil) + var exactCommits []*WrapCommitNode + mockErr := errors.New("mock") + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + if bytes.Equal(node.Hash(), commitF.Hash) { + return mockErr + } + return nil + }) + require.ErrorIs(t, err, mockErr) + require.Len(t, exactCommits, 1) + assertHash(t, exactCommits, commitF.Hash) + }) +} diff --git a/versionmgr/commit_walker_bfs_test.go b/versionmgr/commit_walker_bfs_test.go new file mode 100644 index 00000000..29418e7c --- /dev/null +++ b/versionmgr/commit_walker_bfs_test.go @@ -0,0 +1,129 @@ +package versionmgr + +import ( + "bytes" + "context" + "errors" + "testing" + + "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/stretchr/testify/require" +) + +// TestBfsCommitIterator +// +// A--->B +// / \ +// root E-->F +// \ / +// C---->D +func TestBfsCommitIterator(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + repoID := uuid.New() + repo := models.NewRepo(db) + rootCommit, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "root") + require.NoError(t, err) + commitA, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit a", rootCommit.Hash) + require.NoError(t, err) + + commitB, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commt b", commitA.Hash) + require.NoError(t, err) + + commitC, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit c", rootCommit.Hash) + require.NoError(t, err) + commitD, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit d", commitC.Hash) + require.NoError(t, err) + + commitE, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit e", commitB.Hash, commitD.Hash) + require.NoError(t, err) + + commitF, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit f", commitE.Hash) + require.NoError(t, err) + + commitFNode := NewWrapCommitNode(repo.CommitRepo(repoID), commitF) + + t.Run("NewCommitIterBSF", func(t *testing.T) { + iter := NewCommitIterBSF(ctx, commitFNode, nil, []hash.Hash{}) + var exactCommits []*WrapCommitNode + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + return nil + }) + require.NoError(t, err) + require.Len(t, exactCommits, 7) + assertHash(t, exactCommits, commitF.Hash, commitE.Hash, commitB.Hash, commitD.Hash, commitA.Hash, commitC.Hash, rootCommit.Hash) + }) + + t.Run("NewCommitIterBSF ignore", func(t *testing.T) { + iter := NewCommitIterBSF(ctx, commitFNode, nil, []hash.Hash{commitE.Hash}) + var exactCommits []*WrapCommitNode + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + return nil + }) + require.NoError(t, err) + require.Len(t, exactCommits, 1) + assertHash(t, exactCommits, commitF.Hash) + }) + + t.Run("NewCommitIterBSF ignore", func(t *testing.T) { + iter := NewCommitIterBSF(ctx, commitFNode, map[string]bool{commitE.Hash.Hex(): true}, nil) + var exactCommits []*WrapCommitNode + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + return nil + }) + require.NoError(t, err) + require.Len(t, exactCommits, 1) + assertHash(t, exactCommits, commitF.Hash) + }) + + t.Run("NewCommitIterBSF ignore", func(t *testing.T) { + iter := NewCommitIterBSF(ctx, commitFNode, nil, []hash.Hash{commitD.Hash}) + var exactCommits []*WrapCommitNode + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + return nil + }) + require.NoError(t, err) + require.Len(t, exactCommits, 5) + assertHash(t, exactCommits, commitF.Hash, commitE.Hash, commitB.Hash, commitA.Hash, rootCommit.Hash) + }) + + t.Run("NewCommitIterBSF ErrStop", func(t *testing.T) { + iter := NewCommitIterBSF(ctx, commitFNode, nil, nil) + var exactCommits []*WrapCommitNode + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + if bytes.Equal(node.Hash(), commitF.Hash) { + return ErrStop + } + return nil + }) + require.NoError(t, err) + require.Len(t, exactCommits, 1) + assertHash(t, exactCommits, commitF.Hash) + }) + + t.Run("NewCommitIterBSF err", func(t *testing.T) { + iter := NewCommitIterBSF(ctx, commitFNode, nil, nil) + var exactCommits []*WrapCommitNode + mockErr := errors.New("mock") + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + if bytes.Equal(node.Hash(), commitF.Hash) { + return mockErr + } + return nil + }) + require.ErrorIs(t, err, mockErr) + require.Len(t, exactCommits, 1) + assertHash(t, exactCommits, commitF.Hash) + }) +} diff --git a/versionmgr/commit_walker_ctime.go b/versionmgr/commit_walker_ctime.go index db142ee1..6ec6a812 100644 --- a/versionmgr/commit_walker_ctime.go +++ b/versionmgr/commit_walker_ctime.go @@ -1,12 +1,17 @@ package versionmgr import ( + "context" + "errors" "io" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/emirpasic/gods/trees/binaryheap" ) type commitIteratorByCTime struct { + ctx context.Context seenExternal map[string]bool seen map[string]bool heap *binaryheap.Heap @@ -21,17 +26,18 @@ type commitIteratorByCTime struct { // cannot be traversed (e.g. missing objects). Ignore allows to skip some // commits from being iterated. func NewCommitIterCTime( - c *CommitNode, + ctx context.Context, + c *WrapCommitNode, seenExternal map[string]bool, - ignore []string, + ignore []hash.Hash, ) CommitIter { seen := make(map[string]bool) for _, h := range ignore { - seen[h] = true + seen[h.Hex()] = true } heap := binaryheap.NewWith(func(a, b interface{}) int { - if a.(*CommitNode).Commit().Committer.When.Before(b.(*CommitNode).Commit().Committer.When) { + if a.(*WrapCommitNode).Commit().Committer.When.Before(b.(*WrapCommitNode).Commit().Committer.When) { return 1 } return -1 @@ -39,20 +45,21 @@ func NewCommitIterCTime( heap.Push(c) return &commitIteratorByCTime{ + ctx: ctx, seenExternal: seenExternal, seen: seen, heap: heap, } } -func (w *commitIteratorByCTime) Next() (*CommitNode, error) { - var c *CommitNode +func (w *commitIteratorByCTime) Next() (*WrapCommitNode, error) { + var c *WrapCommitNode for { cIn, ok := w.heap.Pop() if !ok { return nil, io.EOF } - c = cIn.(*CommitNode) + c = cIn.(*WrapCommitNode) if w.seen[c.Commit().Hash.Hex()] || w.seenExternal[c.Commit().Hash.Hex()] { continue @@ -64,7 +71,7 @@ func (w *commitIteratorByCTime) Next() (*CommitNode, error) { if w.seen[h.Hex()] || w.seenExternal[h.Hex()] { continue } - pc, err := c.GetCommit(h) + pc, err := c.GetCommit(w.ctx, h) if err != nil { return nil, err } @@ -75,7 +82,7 @@ func (w *commitIteratorByCTime) Next() (*CommitNode, error) { } } -func (w *commitIteratorByCTime) ForEach(cb func(*CommitNode) error) error { +func (w *commitIteratorByCTime) ForEach(cb func(*WrapCommitNode) error) error { for { c, err := w.Next() if err == io.EOF { @@ -86,7 +93,7 @@ func (w *commitIteratorByCTime) ForEach(cb func(*CommitNode) error) error { } err = cb(c) - if err == ErrStop { + if errors.Is(err, ErrStop) { break } if err != nil { diff --git a/versionmgr/commit_walker_ctime_test.go b/versionmgr/commit_walker_ctime_test.go new file mode 100644 index 00000000..5dc5f09d --- /dev/null +++ b/versionmgr/commit_walker_ctime_test.go @@ -0,0 +1,140 @@ +package versionmgr + +import ( + "bytes" + "context" + "errors" + "testing" + "time" + + "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/stretchr/testify/require" +) + +// TestNewCommitIterCTime +// +// A--->B +// / \ +// root E-->F +// \ / +// C---->D +// +// use time.sleep to control the order, expect f-e-d-b-c-a-root +func TestNewCommitIterCTime(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + repoID := uuid.New() + repo := models.NewRepo(db) + rootCommit, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "root") + require.NoError(t, err) + time.Sleep(time.Millisecond * 500) + + commitA, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit a", rootCommit.Hash) + require.NoError(t, err) + time.Sleep(time.Millisecond * 500) + + commitC, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit c", rootCommit.Hash) + require.NoError(t, err) + time.Sleep(time.Millisecond * 500) + + commitB, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commt b", commitA.Hash) + require.NoError(t, err) + time.Sleep(time.Millisecond * 500) + + commitD, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit d", commitC.Hash) + require.NoError(t, err) + time.Sleep(time.Millisecond * 500) + + commitE, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit e", commitB.Hash, commitD.Hash) + require.NoError(t, err) + time.Sleep(time.Millisecond * 500) + + commitF, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit f", commitE.Hash) + require.NoError(t, err) + + commitFNode := NewWrapCommitNode(repo.CommitRepo(repoID), commitF) + + t.Run("NewCommitIterCTime", func(t *testing.T) { + iter := NewCommitIterCTime(ctx, commitFNode, nil, nil) + var exactCommits []*WrapCommitNode + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + return nil + }) + require.NoError(t, err) + require.Len(t, exactCommits, 7) + assertHash(t, exactCommits, commitF.Hash, commitE.Hash, commitD.Hash, commitB.Hash, commitC.Hash, commitA.Hash, rootCommit.Hash) + }) + + t.Run("NewCommitIterCTime ignore", func(t *testing.T) { + iter := NewCommitIterCTime(ctx, commitFNode, nil, []hash.Hash{commitE.Hash}) + var exactCommits []*WrapCommitNode + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + return nil + }) + require.NoError(t, err) + require.Len(t, exactCommits, 1) + assertHash(t, exactCommits, commitF.Hash) + }) + + t.Run("NewCommitIterCTime ignore", func(t *testing.T) { + iter := NewCommitIterCTime(ctx, commitFNode, map[string]bool{commitE.Hash.Hex(): true}, nil) + var exactCommits []*WrapCommitNode + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + return nil + }) + require.NoError(t, err) + require.Len(t, exactCommits, 1) + assertHash(t, exactCommits, commitF.Hash) + }) + + t.Run("NewCommitIterCTime ignore", func(t *testing.T) { + iter := NewCommitIterCTime(ctx, commitFNode, nil, []hash.Hash{commitD.Hash}) + var exactCommits []*WrapCommitNode + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + return nil + }) + require.NoError(t, err) + require.Len(t, exactCommits, 5) + assertHash(t, exactCommits, commitF.Hash, commitE.Hash, commitB.Hash, commitA.Hash, rootCommit.Hash) + }) + + t.Run("NewCommitIterCTime ErrStop", func(t *testing.T) { + iter := NewCommitIterCTime(ctx, commitFNode, nil, nil) + var exactCommits []*WrapCommitNode + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + if bytes.Equal(node.Hash(), commitF.Hash) { + return ErrStop + } + return nil + }) + require.NoError(t, err) + require.Len(t, exactCommits, 1) + assertHash(t, exactCommits, commitF.Hash) + }) + + t.Run("NewCommitIterCTime err", func(t *testing.T) { + iter := NewCommitIterCTime(ctx, commitFNode, nil, nil) + var exactCommits []*WrapCommitNode + mockErr := errors.New("mock") + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + if bytes.Equal(node.Hash(), commitF.Hash) { + return mockErr + } + return nil + }) + require.ErrorIs(t, err, mockErr) + require.Len(t, exactCommits, 1) + assertHash(t, exactCommits, commitF.Hash) + }) +} diff --git a/versionmgr/commit_walker_test.go b/versionmgr/commit_walker_test.go new file mode 100644 index 00000000..fa1c88a8 --- /dev/null +++ b/versionmgr/commit_walker_test.go @@ -0,0 +1,232 @@ +package versionmgr + +import ( + "bytes" + "context" + "errors" + "testing" + + "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/stretchr/testify/require" +) + +// TestNewCommitPostorderIter +// +// A--->B +// / \ +// root E-->F +// \ / +// C---->D +func TestNewCommitPostorderIter(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + repoID := uuid.New() + repo := models.NewRepo(db) + rootCommit, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "root") + require.NoError(t, err) + commitA, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit a", rootCommit.Hash) + require.NoError(t, err) + + commitB, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commt b", commitA.Hash) + require.NoError(t, err) + + commitC, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit c", rootCommit.Hash) + require.NoError(t, err) + commitD, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit d", commitC.Hash) + require.NoError(t, err) + + commitE, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit e", commitB.Hash, commitD.Hash) + require.NoError(t, err) + + commitF, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit f", commitE.Hash) + require.NoError(t, err) + + commitFNode := NewWrapCommitNode(repo.CommitRepo(repoID), commitF) + + t.Run("NewCommitPostorderIter", func(t *testing.T) { + iter := NewCommitPostorderIter(ctx, commitFNode, nil) + var exactCommits []*WrapCommitNode + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + return nil + }) + require.NoError(t, err) + require.Len(t, exactCommits, 7) + assertHash(t, exactCommits, commitF.Hash, commitE.Hash, commitD.Hash, commitC.Hash, rootCommit.Hash, commitB.Hash, commitA.Hash) + }) + + t.Run("NewCommitPostorderIter ignore", func(t *testing.T) { + iter := NewCommitPostorderIter(ctx, commitFNode, []hash.Hash{commitE.Hash}) + var exactCommits []*WrapCommitNode + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + return nil + }) + require.NoError(t, err) + require.Len(t, exactCommits, 1) + assertHash(t, exactCommits, commitF.Hash) + }) + + t.Run("NewCommitPostorderIter ignore", func(t *testing.T) { + iter := NewCommitPostorderIter(ctx, commitFNode, []hash.Hash{commitD.Hash}) + var exactCommits []*WrapCommitNode + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + return nil + }) + require.NoError(t, err) + require.Len(t, exactCommits, 5) + assertHash(t, exactCommits, commitF.Hash, commitE.Hash, commitB.Hash, commitA.Hash, rootCommit.Hash) + }) + + t.Run("NewCommitPostorderIter ErrStop", func(t *testing.T) { + iter := NewCommitPostorderIter(ctx, commitFNode, nil) + var exactCommits []*WrapCommitNode + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + if bytes.Equal(node.Hash(), commitF.Hash) { + return ErrStop + } + return nil + }) + require.NoError(t, err) + require.Len(t, exactCommits, 1) + assertHash(t, exactCommits, commitF.Hash) + }) + + t.Run("NewCommitPostorderIter err", func(t *testing.T) { + iter := NewCommitPostorderIter(ctx, commitFNode, nil) + var exactCommits []*WrapCommitNode + mockErr := errors.New("mock") + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + if bytes.Equal(node.Hash(), commitF.Hash) { + return mockErr + } + return nil + }) + require.ErrorIs(t, err, mockErr) + require.Len(t, exactCommits, 1) + assertHash(t, exactCommits, commitF.Hash) + }) +} + +// TestCommitPreorderIter +// +// A--->B +// / \ +// root E-->F +// \ / +// C---->D +func TestCommitPreorderIter(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + repoID := uuid.New() + repo := models.NewRepo(db) + rootCommit, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "root") + require.NoError(t, err) + commitA, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit a", rootCommit.Hash) + require.NoError(t, err) + + commitB, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commt b", commitA.Hash) + require.NoError(t, err) + + commitC, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit c", rootCommit.Hash) + require.NoError(t, err) + commitD, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit d", commitC.Hash) + require.NoError(t, err) + + commitE, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit e", commitB.Hash, commitD.Hash) + require.NoError(t, err) + + commitF, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit f", commitE.Hash) + require.NoError(t, err) + + commitFNode := NewWrapCommitNode(repo.CommitRepo(repoID), commitF) + + t.Run("NewCommitPreorderIter", func(t *testing.T) { + iter := NewCommitPreorderIter(ctx, commitFNode, nil, []hash.Hash{}) + var exactCommits []*WrapCommitNode + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + return nil + }) + require.NoError(t, err) + require.Len(t, exactCommits, 7) + assertHash(t, exactCommits, commitF.Hash, commitE.Hash, commitB.Hash, commitA.Hash, rootCommit.Hash, commitD.Hash, commitC.Hash) + }) + + t.Run("NewCommitPreorderIter ignore", func(t *testing.T) { + iter := NewCommitPreorderIter(ctx, commitFNode, nil, []hash.Hash{commitE.Hash}) + var exactCommits []*WrapCommitNode + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + return nil + }) + require.NoError(t, err) + require.Len(t, exactCommits, 1) + assertHash(t, exactCommits, commitF.Hash) + }) + + t.Run("NewCommitPreorderIter ignore", func(t *testing.T) { + iter := NewCommitPreorderIter(ctx, commitFNode, map[string]bool{commitE.Hash.Hex(): true}, nil) + var exactCommits []*WrapCommitNode + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + return nil + }) + require.NoError(t, err) + require.Len(t, exactCommits, 1) + assertHash(t, exactCommits, commitF.Hash) + }) + + t.Run("NewCommitPreorderIter ignore", func(t *testing.T) { + iter := NewCommitPreorderIter(ctx, commitFNode, nil, []hash.Hash{commitD.Hash}) + var exactCommits []*WrapCommitNode + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + return nil + }) + require.NoError(t, err) + require.Len(t, exactCommits, 5) + assertHash(t, exactCommits, commitF.Hash, commitE.Hash, commitB.Hash, commitA.Hash, rootCommit.Hash) + }) + + t.Run("NewCommitPreorderIter ErrStop", func(t *testing.T) { + iter := NewCommitPreorderIter(ctx, commitFNode, nil, nil) + var exactCommits []*WrapCommitNode + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + if bytes.Equal(node.Hash(), commitF.Hash) { + return ErrStop + } + return nil + }) + require.NoError(t, err) + require.Len(t, exactCommits, 1) + assertHash(t, exactCommits, commitF.Hash) + }) + + t.Run("NewCommitPreorderIter err", func(t *testing.T) { + iter := NewCommitPreorderIter(ctx, commitFNode, nil, nil) + var exactCommits []*WrapCommitNode + mockErr := errors.New("mock") + err = iter.ForEach(func(node *WrapCommitNode) error { + exactCommits = append(exactCommits, node) + if bytes.Equal(node.Hash(), commitF.Hash) { + return mockErr + } + return nil + }) + require.ErrorIs(t, err, mockErr) + require.Len(t, exactCommits, 1) + assertHash(t, exactCommits, commitF.Hash) + }) +} diff --git a/versionmgr/merge_base.go b/versionmgr/merge_base.go index 0aabcbb8..192806e3 100644 --- a/versionmgr/merge_base.go +++ b/versionmgr/merge_base.go @@ -2,11 +2,10 @@ package versionmgr import ( "bytes" + "context" "errors" "fmt" "sort" - - "github.com/go-git/go-git/v5/plumbing/storer" ) // errIsReachable is thrown when first commit is an ancestor of the second @@ -15,39 +14,39 @@ var errIsReachable = fmt.Errorf("first is reachable from second") // MergeBase mimics the behavior of `git merge-base actual other`, returning the // best common ancestor between the actual and the passed one. // The best common ancestors can not be reached from other common ancestors. -func (c *CommitNode) MergeBase(other *CommitNode) ([]*CommitNode, error) { +func (c *WrapCommitNode) MergeBase(ctx context.Context, other *WrapCommitNode) ([]*WrapCommitNode, error) { // use sortedByCommitDateDesc strategy sorted := sortByCommitDateDesc(c, other) newer := sorted[0] older := sorted[1] - newerHistory, err := ancestorsIndex(older, newer) + newerHistory, err := ancestorsIndex(ctx, older, newer) if errors.Is(err, errIsReachable) { - return []*CommitNode{older}, nil + return []*WrapCommitNode{older}, nil } if err != nil { return nil, err } - var res []*CommitNode + var res []*WrapCommitNode inNewerHistory := isInIndexCommitFilter(newerHistory) - resIter := NewFilterCommitIter(older, &inNewerHistory, &inNewerHistory) - _ = resIter.ForEach(func(commit *CommitNode) error { + resIter := NewFilterCommitIter(ctx, older, &inNewerHistory, &inNewerHistory) + _ = resIter.ForEach(func(commit *WrapCommitNode) error { res = append(res, commit) return nil }) - return Independents(res) + return Independents(ctx, res) } // IsAncestor returns true if the actual commit is ancestor of the passed one. // It returns an error if the history is not transversable // It mimics the behavior of `git merge --is-ancestor actual other` -func (c *CommitNode) IsAncestor(other *CommitNode) (bool, error) { +func (c *WrapCommitNode) IsAncestor(ctx context.Context, other *WrapCommitNode) (bool, error) { found := false - iter := NewCommitPreorderIter(other, nil, nil) - err := iter.ForEach(func(comm *CommitNode) error { + iter := NewCommitPreorderIter(ctx, other, nil, nil) + err := iter.ForEach(func(comm *WrapCommitNode) error { if !bytes.Equal(comm.Commit().Hash, c.Commit().Hash) { return nil } @@ -62,14 +61,14 @@ func (c *CommitNode) IsAncestor(other *CommitNode) (bool, error) { // ancestorsIndex returns a map with the ancestors of the starting commit if the // excluded one is not one of them. It returns errIsReachable if the excluded commit // is ancestor of the starting, or another error if the history is not traversable. -func ancestorsIndex(excluded, starting *CommitNode) (map[string]struct{}, error) { +func ancestorsIndex(ctx context.Context, excluded, starting *WrapCommitNode) (map[string]struct{}, error) { if bytes.Equal(excluded.Commit().Hash, starting.Commit().Hash) { return nil, errIsReachable } startingHistory := map[string]struct{}{} - startingIter := NewCommitIterBSF(starting, nil, nil) - err := startingIter.ForEach(func(commit *CommitNode) error { + startingIter := NewCommitIterBSF(ctx, starting, nil, nil) + err := startingIter.ForEach(func(commit *WrapCommitNode) error { if bytes.Equal(commit.Commit().Hash, excluded.Commit().Hash) { return errIsReachable } @@ -87,13 +86,13 @@ func ancestorsIndex(excluded, starting *CommitNode) (map[string]struct{}, error) // Independents returns a subset of the passed commits, that are not reachable the others // It mimics the behavior of `git merge-base --independent commit...`. -func Independents(commits []*CommitNode) ([]*CommitNode, error) { +func Independents(ctx context.Context, commits []*WrapCommitNode) ([]*WrapCommitNode, error) { // use sortedByCommitDateDesc strategy candidates := sortByCommitDateDesc(commits...) candidates = removeDuplicated(candidates) seen := map[string]struct{}{} - var isLimit CommitFilter = func(commit *CommitNode) bool { + var isLimit CommitFilter = func(commit *WrapCommitNode) bool { _, ok := seen[commit.Commit().Hash.Hex()] return ok } @@ -106,8 +105,8 @@ func Independents(commits []*CommitNode) ([]*CommitNode, error) { for { from := candidates[pos] others := remove(candidates, from) - fromHistoryIter := NewFilterCommitIter(from, nil, &isLimit) - err := fromHistoryIter.ForEach(func(fromAncestor *CommitNode) error { + fromHistoryIter := NewFilterCommitIter(ctx, from, nil, &isLimit) + err := fromHistoryIter.ForEach(func(fromAncestor *WrapCommitNode) error { for _, other := range others { if bytes.Equal(fromAncestor.Commit().Hash, other.Commit().Hash) { candidates = remove(candidates, other) @@ -116,7 +115,7 @@ func Independents(commits []*CommitNode) ([]*CommitNode, error) { } if len(candidates) == 1 { - return storer.ErrStop + return ErrStop } seen[fromAncestor.Commit().Hash.Hex()] = struct{}{} @@ -146,8 +145,8 @@ func Independents(commits []*CommitNode) ([]*CommitNode, error) { // That way `Independents(A^, A)` will be processed as being `Independents(A, A^)`; // so starting by `A` it will be reached `A^` way sooner than walking from `A^` // to the initial commit, and then from `A` to `A^`. -func sortByCommitDateDesc(commits ...*CommitNode) []*CommitNode { - sorted := make([]*CommitNode, len(commits)) +func sortByCommitDateDesc(commits ...*WrapCommitNode) []*WrapCommitNode { + sorted := make([]*WrapCommitNode, len(commits)) copy(sorted, commits) sort.Slice(sorted, func(i, j int) bool { return sorted[i].Commit().Committer.When.After(sorted[j].Commit().Committer.When) @@ -157,7 +156,7 @@ func sortByCommitDateDesc(commits ...*CommitNode) []*CommitNode { } // indexOf returns the first position where target was found in the passed commits -func indexOf(commits []*CommitNode, target *CommitNode) int { +func indexOf(commits []*WrapCommitNode, target *WrapCommitNode) int { for i, commit := range commits { if bytes.Equal(target.Commit().Hash, commit.Commit().Hash) { return i @@ -168,8 +167,8 @@ func indexOf(commits []*CommitNode, target *CommitNode) int { } // remove returns the passed commits excluding the commit toDelete -func remove(commits []*CommitNode, toDelete *CommitNode) []*CommitNode { - res := make([]*CommitNode, len(commits)) +func remove(commits []*WrapCommitNode, toDelete *WrapCommitNode) []*WrapCommitNode { + res := make([]*WrapCommitNode, len(commits)) j := 0 for _, commit := range commits { if bytes.Equal(commit.Commit().Hash, toDelete.Commit().Hash) { @@ -184,9 +183,9 @@ func remove(commits []*CommitNode, toDelete *CommitNode) []*CommitNode { } // removeDuplicated removes duplicated commits from the passed slice of commits -func removeDuplicated(commits []*CommitNode) []*CommitNode { +func removeDuplicated(commits []*WrapCommitNode) []*WrapCommitNode { seen := make(map[string]struct{}, len(commits)) - res := make([]*CommitNode, len(commits)) + res := make([]*WrapCommitNode, len(commits)) j := 0 for _, commit := range commits { if _, ok := seen[commit.Commit().Hash.Hex()]; ok { @@ -204,7 +203,7 @@ func removeDuplicated(commits []*CommitNode) []*CommitNode { // isInIndexCommitFilter returns a commitFilter that returns true // if the commit is in the passed index. func isInIndexCommitFilter(index map[string]struct{}) CommitFilter { - return func(c *CommitNode) bool { + return func(c *WrapCommitNode) bool { _, ok := index[c.Commit().Hash.Hex()] return ok } diff --git a/versionmgr/merge_base_test.go b/versionmgr/merge_base_test.go index 918fc343..0e94ad4c 100644 --- a/versionmgr/merge_base_test.go +++ b/versionmgr/merge_base_test.go @@ -7,13 +7,10 @@ import ( "time" "github.com/google/uuid" - - "github.com/jiaozifs/jiaozifs/utils/hash" - - "github.com/stretchr/testify/require" - "github.com/jiaozifs/jiaozifs/models" "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/stretchr/testify/require" ) func TestCommitNodeMergeBase(t *testing.T) { @@ -47,7 +44,7 @@ e|b //simple baseCommit := commitMap["b"] mergeCommit := commitMap["c"] - ancestorNode, err := baseCommit.MergeBase(mergeCommit) + ancestorNode, err := baseCommit.MergeBase(ctx, mergeCommit) require.NoError(t, err) require.Len(t, ancestorNode, 1) require.Equal(t, "a", string(ancestorNode[0].Commit().Hash)) @@ -57,7 +54,7 @@ e|b //simple baseCommit := commitMap["f"] mergeCommit := commitMap["f1"] - ancestorNode, err := baseCommit.MergeBase(mergeCommit) + ancestorNode, err := baseCommit.MergeBase(ctx, mergeCommit) require.NoError(t, err) require.Len(t, ancestorNode, 1) require.Equal(t, "f1", string(ancestorNode[0].Commit().Hash)) @@ -66,16 +63,16 @@ e|b t.Run("multiple merge", func(t *testing.T) { baseCommit := commitMap["f"] mergeCommit := commitMap["e"] - ancestorNode, err := baseCommit.MergeBase(mergeCommit) + ancestorNode, err := baseCommit.MergeBase(ctx, mergeCommit) require.NoError(t, err) require.Len(t, ancestorNode, 1) require.Equal(t, "b", string(ancestorNode[0].Commit().Hash)) }) } -func loadCommitTestData(ctx context.Context, commitRepo models.ICommitRepo, testData string) (map[string]*CommitNode, error) { +func loadCommitTestData(ctx context.Context, commitRepo models.ICommitRepo, testData string) (map[string]*WrapCommitNode, error) { lines := strings.Split(testData, "\n") - commitMap := make(map[string]*CommitNode) + commitMap := make(map[string]*WrapCommitNode) for _, line := range lines { if len(strings.TrimSpace(line)) == 0 { continue @@ -83,7 +80,7 @@ func loadCommitTestData(ctx context.Context, commitRepo models.ICommitRepo, test commitData := strings.Split(strings.TrimSpace(line), "|") hashName := strings.TrimSpace(commitData[0]) commit := newCommit(commitRepo.RepositoryID(), hashName, strings.Split(commitData[1], ",")) - commitMap[hashName] = NewCommitNode(ctx, commit, commitRepo) + commitMap[hashName] = NewWrapCommitNode(commitRepo, commit) _, err := commitRepo.Insert(ctx, commit) if err != nil { return nil, err diff --git a/versionmgr/work_repo.go b/versionmgr/work_repo.go new file mode 100644 index 00000000..9be29b01 --- /dev/null +++ b/versionmgr/work_repo.go @@ -0,0 +1,473 @@ +package versionmgr + +import ( + "bytes" + "context" + "encoding/hex" + "errors" + "fmt" + "io" + "os" + "time" + + logging "github.com/ipfs/go-log/v2" + "github.com/jiaozifs/jiaozifs/block" + "github.com/jiaozifs/jiaozifs/block/factory" + "github.com/jiaozifs/jiaozifs/block/params" + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/jiaozifs/jiaozifs/utils/httputil" + "github.com/jiaozifs/jiaozifs/utils/pathutil" +) + +var workRepoLog = logging.Logger("work_repo") + +type WorkRepoState string + +const ( + InWip WorkRepoState = "wip" + InBranch WorkRepoState = "branch" + InCommit WorkRepoState = "commit" +) + +type WorkRepository struct { + operator *models.User + repoModel *models.Repository + adapter block.Adapter + repo models.IRepo + state WorkRepoState + //cache + headTree *hash.Hash + wip *models.WorkingInProcess + branch *models.Branches + commit *models.Commit +} + +func NewWorkRepositoryFromConfig(ctx context.Context, operator *models.User, repoModel *models.Repository, repo models.IRepo, publicAdapterConfig params.AdapterConfig) (*WorkRepository, error) { + var adapter block.Adapter + var err error + if repoModel.UsePublicStorage { + adapter, err = factory.BuildBlockAdapter(ctx, publicAdapterConfig) + } else { + adapter, err = AdapterFromConfig(ctx, repoModel.StorageAdapterParams) + } + if err != nil { + return nil, err + } + return NewWorkRepositoryFromAdapter(ctx, operator, repoModel, repo, adapter), nil +} + +func NewWorkRepositoryFromAdapter(_ context.Context, operator *models.User, repoModel *models.Repository, repo models.IRepo, adapter block.Adapter) *WorkRepository { + return &WorkRepository{ + operator: operator, + repoModel: repoModel, + repo: repo, adapter: adapter, + } +} + +// WriteBlob write blob content to storage +func (repository *WorkRepository) WriteBlob(ctx context.Context, body io.Reader, contentLength int64, properties models.Property) (*models.Blob, error) { + // handle the upload itself + hashReader := hash.NewHashingReader(body, hash.Md5) + tempf, err := os.CreateTemp("", "*") + if err != nil { + return nil, err + } + _, err = io.Copy(tempf, hashReader) + if err != nil { + return nil, err + } + + checkSum := hash.Hash(hashReader.Md5.Sum(nil)) + _, err = tempf.Seek(0, io.SeekStart) + if err != nil { + return nil, err + } + + defer func() { + name := tempf.Name() + _ = tempf.Close() + _ = os.RemoveAll(name) + }() + + address := pathutil.PathOfHash(checkSum) + err = repository.adapter.Put(ctx, block.ObjectPointer{ + StorageNamespace: repository.repoModel.StorageNamespace, + IdentifierType: block.IdentifierTypeRelative, + Identifier: address, + }, contentLength, tempf, block.PutOpts{}) + if err != nil { + return nil, err + } + + return models.NewBlob(properties, repository.repoModel.ID, checkSum, hashReader.CopiedSize) +} + +// ReadBlob read blob content with range +func (repository *WorkRepository) ReadBlob(ctx context.Context, blob *models.Blob, rangeSpec *string) (io.ReadCloser, error) { + address := pathutil.PathOfHash(blob.CheckSum) + pointer := block.ObjectPointer{ + StorageNamespace: repository.repoModel.StorageNamespace, + IdentifierType: block.IdentifierTypeRelative, + Identifier: address, + } + + // setup response + var reader io.ReadCloser + // handle partial response if byte range supplied + if rangeSpec != nil { + rng, err := httputil.ParseRange(*rangeSpec, blob.Size) + if err != nil { + return nil, err + } + reader, err = repository.adapter.GetRange(ctx, pointer, rng.StartOffset, rng.EndOffset) + if err != nil { + return nil, err + } + + } else { + var err error + reader, err = repository.adapter.Get(ctx, pointer, blob.Size) + if err != nil { + return nil, err + } + } + return reader, nil +} + +// RootTree return worktree at root +func (repository *WorkRepository) RootTree(ctx context.Context) (*WorkTree, error) { + if repository.headTree == nil { + //use repo default headTree + ref, err := repository.repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.repoModel.ID).SetName(repository.repoModel.HEAD)) + if err != nil { + return nil, err + } + + repository.branch = ref + treeHash := hash.EmptyHash + var commit *models.Commit + if !ref.CommitHash.IsEmpty() { + commit, err = repository.repo.CommitRepo(repository.repoModel.ID).Commit(ctx, ref.CommitHash) + if err != nil { + return nil, err + } + treeHash = commit.TreeHash + } + repository.setCurState(InBranch, nil, ref, commit) + repository.headTree = &treeHash + } + return NewWorkTree(ctx, repository.repo.FileTreeRepo(repository.repoModel.ID), models.NewRootTreeEntry(*repository.headTree)) +} + +func (repository *WorkRepository) CheckOut(ctx context.Context, refType WorkRepoState, refName string) error { + treeHash := hash.EmptyHash + if refType == InWip { + ref, err := repository.repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.repoModel.ID).SetName(refName)) + if err != nil { + return err + } + wip, err := repository.repo.WipRepo().Get(ctx, models.NewGetWipParams().SetCreatorID(repository.operator.ID).SetRepositoryID(repository.repoModel.ID).SetRefID(ref.ID)) + if err != nil { + return err + } + treeHash = wip.CurrentTree + repository.setCurState(InWip, wip, ref, nil) + } else if refType == InBranch { + branch, err := repository.repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.repoModel.ID).SetName(refName)) + if err != nil { + return err + } + var commit *models.Commit + if !branch.CommitHash.IsEmpty() { + commit, err = repository.repo.CommitRepo(repository.repoModel.ID).Commit(ctx, branch.CommitHash) + if err != nil { + return err + } + treeHash = commit.TreeHash + } + repository.setCurState(InBranch, nil, branch, commit) + } else if refType == InCommit { + commitHash, err := hex.DecodeString(refName) + if err != nil { + return err + } + commit, err := repository.repo.CommitRepo(repository.repoModel.ID).Commit(ctx, commitHash) + if err != nil { + return err + } + treeHash = commit.TreeHash + repository.setCurState(InCommit, nil, nil, commit) + } else { + return fmt.Errorf("not support type") + } + repository.headTree = &treeHash + return nil +} + +// CommitChanges append a new commit to current headTree, read changes from wip, than create a new commit with parent point to current headTree, +// and replace tree hash with wip's currentTreeHash. +func (repository *WorkRepository) CommitChanges(ctx context.Context, msg string) (*models.Commit, error) { + if !(repository.state == InWip || repository.state == InBranch) { + return nil, errors.New("must commit changes on branch") + } + + head := repository.headTree + creator, err := repository.repo.UserRepo().Get(ctx, models.NewGetUserParams().SetID(repository.wip.CreatorID)) + if err != nil { + return nil, err + } + + if !bytes.Equal(repository.branch.CommitHash, repository.wip.BaseCommit) { + return nil, fmt.Errorf("base commit not equal with branch, please update wip") + } + + parentHash := []hash.Hash{} + if !repository.branch.CommitHash.IsEmpty() { + parentHash = []hash.Hash{repository.branch.CommitHash} + } + + commit := &models.Commit{ + Hash: nil, + RepositoryID: repository.repoModel.ID, + Author: models.Signature{ + Name: creator.Name, + Email: creator.Email, + When: repository.wip.UpdatedAt, + }, + Committer: models.Signature{ + Name: repository.operator.Name, + Email: repository.operator.Email, + When: time.Now(), + }, + MergeTag: "", + Message: msg, + TreeHash: repository.wip.CurrentTree, + ParentHashes: parentHash, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + commitHash, err := commit.GetHash() + if err != nil { + return nil, err + } + commit.Hash = commitHash + err = repository.repo.Transaction(ctx, func(repo models.IRepo) error { + _, err = repo.CommitRepo(repository.repoModel.ID).Insert(ctx, commit) + if err != nil { + return err + } + + err = repo.WipRepo().UpdateByID(ctx, models.NewUpdateWipParams(repository.wip.ID).SetBaseCommit(commitHash)) + if err != nil { + return err + } + + head = &repository.wip.CurrentTree + return repo.BranchRepo().UpdateByID(ctx, models.NewUpdateBranchParams(repository.branch.ID).SetCommitHash(commitHash)) + }) + if err != nil { + return nil, err + } + + repository.branch.CommitHash = commitHash + repository.wip.BaseCommit = commitHash + repository.headTree = head + return commit, err +} + +// DiffCommit find file changes in two commit +func (repository *WorkRepository) DiffCommit(ctx context.Context, toCommitID hash.Hash) (*Changes, error) { + workTree, err := repository.RootTree(ctx) + if err != nil { + return nil, err + } + toCommit, err := repository.repo.CommitRepo(repository.repoModel.ID).Commit(ctx, toCommitID) + if err != nil { + return nil, err + } + + return workTree.Diff(ctx, toCommit.TreeHash) +} + +// Merge implement merge like git, docs https://en.wikipedia.org/wiki/Merge_(version_control) +func (repository *WorkRepository) Merge(ctx context.Context, merger *models.User, toMergeCommitHash hash.Hash, msg string, resolver ConflictResolver) (*models.Commit, error) { + if repository.state != InBranch { + return nil, errors.New("must merge on branch") + } + var commit *models.Commit + var err error + if !repository.branch.CommitHash.IsEmpty() { + commit, err = repository.repo.CommitRepo(repository.repoModel.ID).Commit(ctx, repository.branch.CommitHash) + if err != nil { + return nil, err + } + } + + newCommit, err := merge(ctx, + repository.repo.CommitRepo(repository.repoModel.ID), + repository.repo.FileTreeRepo(repository.repoModel.ID), + merger, + commit, + repository.repoModel, + toMergeCommitHash, + msg, + resolver) + if err != nil { + return nil, err + } + + updateParams := models.NewUpdateBranchParams(repository.branch.ID).SetCommitHash(newCommit.Hash) + err = repository.repo.BranchRepo().UpdateByID(ctx, updateParams) + if err != nil { + return nil, err + } + return newCommit, nil +} + +func (repository *WorkRepository) setCurState(state WorkRepoState, wip *models.WorkingInProcess, branch *models.Branches, commit *models.Commit) { + repository.state = state + repository.wip = wip + repository.branch = branch + repository.commit = commit +} + +func (repository *WorkRepository) CurWip() *models.WorkingInProcess { + return repository.wip +} + +func (repository *WorkRepository) CurBranch() *models.Branches { + return repository.branch +} + +func (repository *WorkRepository) Reset() { + repository.headTree = nil + repository.setCurState("", nil, nil, nil) +} + +func merge(ctx context.Context, + commitRepo models.ICommitRepo, + fileTreeRepo models.IFileTreeRepo, + merger *models.User, + baseCommit *models.Commit, + repoModel *models.Repository, + toMergeCommitHash hash.Hash, msg string, resolver ConflictResolver) (*models.Commit, error) { + toMergeCommit, err := commitRepo.Commit(ctx, toMergeCommitHash) + if err != nil { + return nil, err + } + + //find accessor + baseCommitNode := NewWrapCommitNode(commitRepo, baseCommit) + toMergeCommitNode := NewWrapCommitNode(commitRepo, toMergeCommit) + + { + //do nothing while merge is ancestor of base + mergeIsAncestorOfBase, err := toMergeCommitNode.IsAncestor(ctx, baseCommitNode) + if err != nil { + return nil, err + } + + if mergeIsAncestorOfBase { + workRepoLog.Warnf("merge commit %s is ancestor of base commit %s", toMergeCommitHash, baseCommit.Hash) + return baseCommit, nil + } + } + + { + //try fast-forward merge no need to create new commit node + baseIsAncestorOfMerge, err := baseCommitNode.IsAncestor(ctx, toMergeCommitNode) + if err != nil { + return nil, err + } + + if baseIsAncestorOfMerge { + workRepoLog.Warnf("base commit %s is ancestor of merge commit %s", toMergeCommitHash, baseCommit.Hash) + return toMergeCommit, nil + } + } + + // three-way merge + bestAncestor, err := baseCommitNode.MergeBase(ctx, toMergeCommitNode) + if err != nil { + return nil, err + } + + if len(bestAncestor) == 0 { + return nil, fmt.Errorf("no common ancesstor find") + } + + bestCommit := bestAncestor[0] + if len(bestAncestor) > 1 { + //merge cross merge create virtual commit + virtualCommit, err := merge(ctx, commitRepo, fileTreeRepo, merger, bestAncestor[0].Commit(), repoModel, bestAncestor[1].Commit().Hash, "virtual commit", resolver) + if err != nil { + return nil, err + } + + bestCommit = NewWrapCommitNode(commitRepo, virtualCommit) + } + + ancestorWorkTree, err := NewWorkTree(ctx, fileTreeRepo, models.NewRootTreeEntry(bestAncestor[0].TreeHash())) + if err != nil { + return nil, err + } + + baseDiff, err := ancestorWorkTree.Diff(ctx, baseCommit.TreeHash) + if err != nil { + return nil, err + } + + mergeDiff, err := ancestorWorkTree.Diff(ctx, toMergeCommit.TreeHash) + if err != nil { + return nil, err + } + + //merge diff + baseWorkTree, err := NewWorkTree(ctx, fileTreeRepo, models.NewRootTreeEntry(bestCommit.Commit().TreeHash)) + if err != nil { + return nil, err + } + + cmw := NewChangesMergeIter(baseDiff, mergeDiff, resolver) + for cmw.Has() { + change, err := cmw.Next() + if err != nil { + return nil, err + } + //apply change + err = baseWorkTree.ApplyOneChange(ctx, change) + if err != nil { + return nil, err + } + } + + author := models.Signature{ + Name: merger.Name, + Email: merger.Email, + When: time.Now(), + } + + mergeCommit := &models.Commit{ + Author: author, + RepositoryID: repoModel.ID, + Committer: author, + MergeTag: "", + Message: msg, + TreeHash: baseWorkTree.Root().Hash(), + ParentHashes: []hash.Hash{baseCommit.Hash, toMergeCommitHash}, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + hash, err := mergeCommit.GetHash() + if err != nil { + return nil, err + } + mergeCommit.Hash = hash + + mergeCommitResult, err := commitRepo.Insert(ctx, mergeCommit) + if err != nil { + return nil, err + } + return mergeCommitResult, nil +} diff --git a/versionmgr/work_repo_diff_test.go b/versionmgr/work_repo_diff_test.go new file mode 100644 index 00000000..5c7f488d --- /dev/null +++ b/versionmgr/work_repo_diff_test.go @@ -0,0 +1,94 @@ +package versionmgr + +import ( + "context" + "testing" + + "github.com/jiaozifs/jiaozifs/block/mem" + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" + "github.com/stretchr/testify/require" +) + +func TestWorkRepositoryDiffCommit(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + repo := models.NewRepo(db) + adapter := mem.New(ctx) + + user, err := makeUser(ctx, repo.UserRepo(), "admin") + require.NoError(t, err) + + project, err := makeRepository(ctx, repo.RepositoryRepo(), user, "testproject") + require.NoError(t, err) + + //commit1 a.txt b/c.txt b/e.txt + //commit2 a.txt b/d.txt b/e.txt + testData1 := ` +1|a.txt |a +1|b/c.txt |c +1|b/e.txt |e1 +` + //base branch + baseBranch, err := makeBranch(ctx, repo.BranchRepo(), user, "feat/base", project.ID, hash.EmptyHash) + require.NoError(t, err) + root1, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), EmptyDirEntry, testData1) + require.NoError(t, err) + baseWip, err := makeWip(ctx, repo.WipRepo(), user.ID, project.ID, baseBranch.ID, EmptyRoot.Hash, root1.Hash) + require.NoError(t, err) + + workRepo := NewWorkRepositoryFromAdapter(ctx, user, project, repo, adapter) + _, err = workRepo.CommitChanges(ctx, "base commit") //asset not correct state + require.Error(t, err) + require.NoError(t, workRepo.CheckOut(ctx, InWip, "feat/base")) + + baseCommit, err := workRepo.CommitChanges(ctx, "base commit") + require.NoError(t, err) + require.NoError(t, rmWip(ctx, repo.WipRepo(), baseWip.ID)) + + testData2 := ` +3|a.txt |a1 +2|b/c.txt |d +3|b/e.txt |e2 +1|b/g.txt |g1 +` + diffBranch, err := makeBranch(ctx, repo.BranchRepo(), user, "feat/diff", project.ID, hash.EmptyHash) + require.NoError(t, err) + + root2, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(root1.Hash), testData2) + require.NoError(t, err) + secondWip, err := makeWip(ctx, repo.WipRepo(), user.ID, project.ID, diffBranch.ID, EmptyRoot.Hash, root2.Hash) + require.NoError(t, err) + + require.NoError(t, workRepo.CheckOut(ctx, InWip, "feat/diff")) + secondCommit, err := workRepo.CommitChanges(ctx, "merge commit") + require.NoError(t, err) + require.NoError(t, rmWip(ctx, repo.WipRepo(), secondWip.ID)) + + require.NoError(t, workRepo.CheckOut(ctx, InCommit, baseCommit.Hash.Hex())) + changes, err := workRepo.DiffCommit(ctx, secondCommit.Hash) + require.NoError(t, err) + require.Equal(t, 4, changes.Num()) + require.Equal(t, "a.txt", changes.Index(0).Path()) + action, err := changes.Index(0).Action() + require.NoError(t, err) + require.Equal(t, merkletrie.Modify, action) + + require.Equal(t, "b/c.txt", changes.Index(1).Path()) + action, err = changes.Index(1).Action() + require.NoError(t, err) + require.Equal(t, merkletrie.Delete, action) + require.Equal(t, "b/e.txt", changes.Index(2).Path()) + action, err = changes.Index(2).Action() + require.NoError(t, err) + require.Equal(t, merkletrie.Modify, action) + + require.Equal(t, "b/g.txt", changes.Index(3).Path()) + action, err = changes.Index(3).Action() + require.NoError(t, err) + require.Equal(t, merkletrie.Insert, action) +} diff --git a/versionmgr/work_repo_merge_test.go b/versionmgr/work_repo_merge_test.go new file mode 100644 index 00000000..18526386 --- /dev/null +++ b/versionmgr/work_repo_merge_test.go @@ -0,0 +1,262 @@ +package versionmgr + +import ( + "context" + "testing" + + "github.com/jiaozifs/jiaozifs/block/mem" + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/stretchr/testify/require" +) + +// TestCommitOpMerge +//example +// A -----C +// | | \ +// | | \ +// / \ F \ +// / \ / \ +// root AB CG +// \ / \ / +// \ / \ / +// B-D-E--- G + +func TestCommitOpMerge(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + adapter := mem.New(ctx) + repo := models.NewRepo(db) + + user, err := makeUser(ctx, repo.UserRepo(), "admin") + require.NoError(t, err) + + project, err := makeRepository(ctx, repo.RepositoryRepo(), user, "testproject") + require.NoError(t, err) + + testData := ` +1|a.txt |h1 +1|b/c.txt |h2 +` + oriRoot, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), EmptyDirEntry, testData) + require.NoError(t, err) + //base branch + baseBranch, err := makeBranch(ctx, repo.BranchRepo(), user, "feat/base", project.ID, hash.EmptyHash) + require.NoError(t, err) + + oriWip, err := makeWip(ctx, repo.WipRepo(), user.ID, project.ID, baseBranch.ID, hash.Hash{}, oriRoot.Hash) + require.NoError(t, err) + + workRepo := NewWorkRepositoryFromAdapter(ctx, user, project, repo, adapter) + + require.NoError(t, workRepo.CheckOut(ctx, InWip, "feat/base")) + oriCommit, err := workRepo.CommitChanges(ctx, "") + require.NoError(t, err) + require.NoError(t, rmWip(ctx, repo.WipRepo(), oriWip.ID)) + //modify a.txt + //---------------CommitA + testData = ` +3|a.txt |h5 +3|b/c.txt |h2 +` + branchA, err := makeBranch(ctx, repo.BranchRepo(), user, "feat/branchA", project.ID, oriCommit.Hash) + require.NoError(t, err) + + baseModify, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(oriCommit.TreeHash), testData) + require.NoError(t, err) + + baseWip, err := makeWip(ctx, repo.WipRepo(), user.ID, project.ID, branchA.ID, oriCommit.Hash, baseModify.Hash) + require.NoError(t, err) + + require.NoError(t, workRepo.CheckOut(ctx, InWip, "feat/branchA")) + commitA, err := workRepo.CommitChanges(ctx, "commit a") + require.NoError(t, err) + require.NoError(t, rmWip(ctx, repo.WipRepo(), baseWip.ID)) + + //modify a.txt + //---------------CommitB + testData = ` +3|a.txt |h4 +3|b/c.txt |h2 +` + branchB, err := makeBranch(ctx, repo.BranchRepo(), user, "feat/branchB", project.ID, oriCommit.Hash) + require.NoError(t, err) + mergeModify, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(oriCommit.TreeHash), testData) + require.NoError(t, err) + mergeWip, err := makeWip(ctx, repo.WipRepo(), user.ID, project.ID, branchB.ID, oriCommit.Hash, mergeModify.Hash) + require.NoError(t, err) + + require.NoError(t, workRepo.CheckOut(ctx, InWip, "feat/branchB")) + commitB, err := workRepo.CommitChanges(ctx, "commit b") + require.NoError(t, err) + require.NoError(t, rmWip(ctx, repo.WipRepo(), mergeWip.ID)) + + //--------------CommitAB + require.NoError(t, workRepo.CheckOut(ctx, InBranch, "feat/branchA")) + commitAB, err := workRepo.Merge(ctx, user, commitB.Hash, "commit ab", LeastHashResolve) + require.NoError(t, err) + + //--------------CommitF + testData = ` +1|x.txt |h4 +` + branchF, err := makeBranch(ctx, repo.BranchRepo(), user, "feat/branchF", project.ID, commitAB.Hash) + require.NoError(t, err) + rootF, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(commitAB.TreeHash), testData) + require.NoError(t, err) + mergeWipF, err := makeWip(ctx, repo.WipRepo(), user.ID, project.ID, branchF.ID, commitAB.Hash, rootF.Hash) + require.NoError(t, err) + require.NoError(t, workRepo.CheckOut(ctx, InWip, "feat/branchF")) + _, err = workRepo.CommitChanges(ctx, "commit f") + require.NoError(t, err) + require.NoError(t, rmWip(ctx, repo.WipRepo(), mergeWipF.ID)) + + //commitC + require.NoError(t, workRepo.CheckOut(ctx, InBranch, "feat/branchF")) + commitC, err := workRepo.Merge(ctx, user, commitA.Hash, "commit c", LeastHashResolve) + require.NoError(t, err) + + //commitD + testData = ` +3|a.txt |h5 +3|b/c.txt |h6 +1|g/c.txt |h7 +` + branchDE, err := makeBranch(ctx, repo.BranchRepo(), user, "feat/branchD_E", project.ID, commitB.Hash) + require.NoError(t, err) + modifyD, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(commitB.TreeHash), testData) + require.NoError(t, err) + mergeWipD, err := makeWip(ctx, repo.WipRepo(), user.ID, project.ID, branchDE.ID, commitB.Hash, modifyD.Hash) + require.NoError(t, err) + require.NoError(t, workRepo.CheckOut(ctx, InWip, "feat/branchD_E")) + commitD, err := workRepo.CommitChanges(ctx, "commit d") + require.NoError(t, err) + require.NoError(t, rmWip(ctx, repo.WipRepo(), mergeWipD.ID)) + + //commitE + testData = ` +2|a.txt |h4 +` + modifyE, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(commitD.TreeHash), testData) + require.NoError(t, err) + mergeWipE, err := makeWip(ctx, repo.WipRepo(), user.ID, project.ID, branchDE.ID, commitD.Hash, modifyE.Hash) + require.NoError(t, err) + //require.NoError(t, workRepo.CheckOut(ctx, InWip, "feat/branchD_E")) + commitE, err := workRepo.CommitChanges(ctx, "commit e") + require.NoError(t, err) + require.NoError(t, rmWip(ctx, repo.WipRepo(), mergeWipE.ID)) + + //test fast-ward + require.NoError(t, workRepo.CheckOut(ctx, InBranch, "feat/branchB")) + commitBE, err := workRepo.Merge(ctx, user, commitE.Hash, "commit ab", LeastHashResolve) + require.NoError(t, err) + require.Equal(t, commitE.Hash.Hex(), commitBE.Hash.Hex()) + + //commitG + require.NoError(t, workRepo.CheckOut(ctx, InBranch, "feat/branchD_E")) + commitG, err := workRepo.Merge(ctx, user, commitAB.Hash, "commit g", LeastHashResolve) + require.NoError(t, err) + + _, err = makeBranch(ctx, repo.BranchRepo(), user, "feat/branchG", project.ID, commitG.Hash) + require.NoError(t, err) + + require.NoError(t, workRepo.CheckOut(ctx, InBranch, "feat/branchG")) + _, err = workRepo.Merge(ctx, user, commitC.Hash, "commit cg", LeastHashResolve) + require.NoError(t, err) +} + +// TestCrissCrossMerge +// +// example +// +// C--------D +// / \ / \ +// / \ / \ +// root *AB CG +// \ / \ / +// \ / \ / +// B-------G +func TestCrissCrossMerge(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + repo := models.NewRepo(db) + adapter := mem.New(ctx) + + user, err := makeUser(ctx, repo.UserRepo(), "admin") + require.NoError(t, err) + + project, err := makeRepository(ctx, repo.RepositoryRepo(), user, "testproject") + require.NoError(t, err) + workRepo := NewWorkRepositoryFromAdapter(ctx, user, project, repo, adapter) + + testData := ` +1|a.txt |h1 +1|b.txt |h2 +` + oriRoot, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), EmptyDirEntry, testData) + require.NoError(t, err) + //base branch + baseBranch, err := makeBranch(ctx, repo.BranchRepo(), user, "feat/base", project.ID, hash.EmptyHash) + require.NoError(t, err) + + oriWip, err := makeWip(ctx, repo.WipRepo(), user.ID, project.ID, baseBranch.ID, hash.Hash{}, oriRoot.Hash) + require.NoError(t, err) + require.NoError(t, workRepo.CheckOut(ctx, InWip, "feat/base")) + oriCommit, err := workRepo.CommitChanges(ctx, "base commit") + require.NoError(t, err) + require.NoError(t, rmWip(ctx, repo.WipRepo(), oriWip.ID)) + + //------------------CommitC + testData = ` +3|a.txt |h1 +3|b.txt |h3 +` + branchC, err := makeBranch(ctx, repo.BranchRepo(), user, "feat/branchC", project.ID, oriCommit.Hash) + require.NoError(t, err) + baseModify, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(oriCommit.TreeHash), testData) + require.NoError(t, err) + wipC, err := makeWip(ctx, repo.WipRepo(), user.ID, project.ID, branchC.ID, oriCommit.Hash, baseModify.Hash) + require.NoError(t, err) + + require.NoError(t, workRepo.CheckOut(ctx, InWip, "feat/branchC")) + commitC, err := workRepo.CommitChanges(ctx, "commit c") + require.NoError(t, err) + require.NoError(t, rmWip(ctx, repo.WipRepo(), wipC.ID)) + //modify a.txt + //-----------------CommitB + testData = ` +3|a.txt |h4 +3|b.txt |h2 +` + branchB, err := makeBranch(ctx, repo.BranchRepo(), user, "feat/branchB", project.ID, oriCommit.Hash) + require.NoError(t, err) + mergeModify, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(oriCommit.TreeHash), testData) + require.NoError(t, err) + wipB, err := makeWip(ctx, repo.WipRepo(), user.ID, project.ID, branchB.ID, oriCommit.Hash, mergeModify.Hash) + require.NoError(t, err) + require.NoError(t, workRepo.CheckOut(ctx, InWip, "feat/branchB")) + commitB, err := workRepo.CommitChanges(ctx, "commit b") + require.NoError(t, err) + require.NoError(t, rmWip(ctx, repo.WipRepo(), wipB.ID)) + + //-----------------CommitAB + require.NoError(t, workRepo.CheckOut(ctx, InBranch, "feat/branchB")) + commiyBC, err := workRepo.Merge(ctx, user, commitC.Hash, "commit bc", LeastHashResolve) + require.NoError(t, err) + + require.NoError(t, workRepo.CheckOut(ctx, InBranch, "feat/branchC")) + commitCB, err := workRepo.Merge(ctx, user, commitB.Hash, "commit cb", LeastHashResolve) + require.NoError(t, err) + + _, err = makeBranch(ctx, repo.BranchRepo(), user, "feat/branchBC", project.ID, commiyBC.Hash) + require.NoError(t, err) + + require.NoError(t, workRepo.CheckOut(ctx, InBranch, "feat/branchBC")) + _, err = workRepo.Merge(ctx, user, commitCB.Hash, "cross commit", LeastHashResolve) + require.NoError(t, err) +} diff --git a/versionmgr/work_repo_test.go b/versionmgr/work_repo_test.go new file mode 100644 index 00000000..0c3c3792 --- /dev/null +++ b/versionmgr/work_repo_test.go @@ -0,0 +1,353 @@ +package versionmgr + +import ( + "bytes" + "context" + "encoding/hex" + "fmt" + "io" + "os" + "path" + "strings" + "testing" + "time" + + "github.com/brianvoe/gofakeit/v6" + "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/block/mem" + "github.com/jiaozifs/jiaozifs/config" + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/models/filemode" + "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/jiaozifs/jiaozifs/utils" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTreeWriteBlob(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + adapter := mem.New(ctx) + repo := models.NewRepo(db) + + userModel := &models.User{} + require.NoError(t, gofakeit.Struct(userModel)) + userModel, err := repo.UserRepo().Insert(ctx, userModel) + require.NoError(t, err) + + repoModel := &models.Repository{} + require.NoError(t, gofakeit.Struct(repoModel)) + repoModel.CreatorID = userModel.ID + repoModel.StorageNamespace = "mem://data" + repoModel, err = repo.RepositoryRepo().Insert(ctx, repoModel) + require.NoError(t, err) + + workRepo := NewWorkRepositoryFromAdapter(ctx, userModel, repoModel, repo, adapter) + + binary := []byte("Build simple, secure, scalable systems with Go") + bLen := int64(len(binary)) + r := bytes.NewReader(binary) + blob, err := workRepo.WriteBlob(ctx, r, bLen, models.DefaultLeafProperty()) + require.NoError(t, err) + assert.Equal(t, bLen, blob.Size) + assert.Equal(t, "99b91d4c517d0cded9506be9298b8d02", blob.Hash.Hex()) + assert.Equal(t, "f3b39786b86a96372589aa1166966643", blob.CheckSum.Hex()) + + reader, err := workRepo.ReadBlob(ctx, blob, nil) + require.NoError(t, err) + content, err := io.ReadAll(reader) + require.NoError(t, err) + require.Equal(t, binary, content) +} + +func TestNewWorkRepositoryFromConfig(t *testing.T) { + ctx := context.Background() + tmpDir, err := os.MkdirTemp(os.TempDir(), "*") + require.NoError(t, err) + + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + repo := models.NewRepo(db) + user, err := makeUser(ctx, repo.UserRepo(), "admin") + require.NoError(t, err) + + t.Run("use public", func(t *testing.T) { + pubCfg := &config.BlockStoreConfig{ + Type: "local", + Local: (*struct { + Path string `mapstructure:"path"` + ImportEnabled bool `mapstructure:"import_enabled"` + ImportHidden bool `mapstructure:"import_hidden"` + AllowedExternalPrefixes []string `mapstructure:"allowed_external_prefixes"` + })(&struct { + Path string + ImportEnabled bool + ImportHidden bool + AllowedExternalPrefixes []string + }{Path: path.Join(tmpDir, "d1"), ImportEnabled: false, ImportHidden: false, AllowedExternalPrefixes: nil}), + } + + project, err := repo.RepositoryRepo().Insert(ctx, &models.Repository{ + Name: "testproject", + Description: utils.String("test"), + UsePublicStorage: true, + HEAD: "main", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + CreatorID: user.ID, + StorageNamespace: "mem://data", + }) + require.NoError(t, err) + newRepo, err := NewWorkRepositoryFromConfig(ctx, user, project, repo, pubCfg) + require.NoError(t, err) + require.Equal(t, "local", newRepo.adapter.BlockstoreType()) + }) + + t.Run("use private", func(t *testing.T) { + pubCfg := &config.BlockStoreConfig{ + Type: "local", + Local: (*struct { + Path string `mapstructure:"path"` + ImportEnabled bool `mapstructure:"import_enabled"` + ImportHidden bool `mapstructure:"import_hidden"` + AllowedExternalPrefixes []string `mapstructure:"allowed_external_prefixes"` + })(&struct { + Path string + ImportEnabled bool + ImportHidden bool + AllowedExternalPrefixes []string + }{Path: path.Join(tmpDir, "d1"), ImportEnabled: false, ImportHidden: false, AllowedExternalPrefixes: nil}), + } + storageCfg := fmt.Sprintf(`{"Type":"local","Local":{"Path":"%s"}}`, path.Join(tmpDir, "d2")) + project, err := repo.RepositoryRepo().Insert(ctx, &models.Repository{ + Name: "testproject2", + Description: utils.String("test"), + UsePublicStorage: false, + HEAD: "main", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + CreatorID: user.ID, + StorageAdapterParams: storageCfg, + }) + require.NoError(t, err) + newRepo, err := NewWorkRepositoryFromConfig(ctx, user, project, repo, pubCfg) + require.NoError(t, err) + require.Equal(t, "local", newRepo.adapter.BlockstoreType()) + }) + + t.Run("false storage format", func(t *testing.T) { + pubCfg := &config.BlockStoreConfig{ + Type: "local", + Local: (*struct { + Path string `mapstructure:"path"` + ImportEnabled bool `mapstructure:"import_enabled"` + ImportHidden bool `mapstructure:"import_hidden"` + AllowedExternalPrefixes []string `mapstructure:"allowed_external_prefixes"` + })(&struct { + Path string + ImportEnabled bool + ImportHidden bool + AllowedExternalPrefixes []string + }{Path: path.Join(tmpDir, "d1"), ImportEnabled: false, ImportHidden: false, AllowedExternalPrefixes: nil}), + } + storageCfg := fmt.Sprintf(`{"Type":"local",Local":{"Path":"%s"}}`, path.Join(tmpDir, "d2")) + project, err := repo.RepositoryRepo().Insert(ctx, &models.Repository{ + Name: "testproject3", + Description: utils.String("test"), + UsePublicStorage: false, + HEAD: "main", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + CreatorID: user.ID, + StorageAdapterParams: storageCfg, + }) + require.NoError(t, err) + _, err = NewWorkRepositoryFromConfig(ctx, user, project, repo, pubCfg) + require.Error(t, err) + }) +} + +func TestWorkRepository_RootTree(t *testing.T) { + ctx := context.Background() + + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + repo := models.NewRepo(db) + user, err := makeUser(ctx, repo.UserRepo(), "admin") + require.NoError(t, err) + + project, err := makeRepository(ctx, repo.RepositoryRepo(), user, "testproject") + require.NoError(t, err) + + mainBranch, err := makeBranch(ctx, repo.BranchRepo(), user, "main", project.ID, hash.EmptyHash) + require.NoError(t, err) + + workRepo := NewWorkRepositoryFromAdapter(ctx, user, project, repo, mem.New(ctx)) + _, err = workRepo.RootTree(ctx) + require.NoError(t, err) + + testData := ` +1|a.txt |h1 +1|b/c.txt |h2 +` + oriRoot, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), EmptyDirEntry, testData) + require.NoError(t, err) + mainWip, err := makeWip(ctx, repo.WipRepo(), user.ID, project.ID, mainBranch.ID, hash.Hash{}, oriRoot.Hash) + require.NoError(t, err) + + require.Error(t, workRepo.CheckOut(ctx, WorkRepoState("mock"), "main")) + + require.NoError(t, workRepo.CheckOut(ctx, InWip, "main")) + require.Equal(t, mainWip.ID, workRepo.CurWip().ID) + require.Equal(t, "main", workRepo.CurBranch().Name) + commit, err := workRepo.CommitChanges(ctx, "init commit") + require.NoError(t, err) + + workTree, err := workRepo.RootTree(ctx) + require.NoError(t, err) + require.Equal(t, commit.TreeHash.Hex(), hex.EncodeToString(workTree.Root().Hash())) + + workRepo.Reset() + workTree, err = workRepo.RootTree(ctx) + require.NoError(t, err) + require.Equal(t, commit.TreeHash.Hex(), hex.EncodeToString(workTree.Root().Hash())) + require.Equal(t, "main", workRepo.CurBranch().Name) +} + +func makeUser(ctx context.Context, userRepo models.IUserRepo, name string) (*models.User, error) { + user := &models.User{ + Name: name, + Email: "xxx@gg.com", + EncryptedPassword: "123", + CurrentSignInAt: time.Time{}, + LastSignInAt: time.Time{}, + CurrentSignInIP: "", + LastSignInIP: "", + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + } + return userRepo.Insert(ctx, user) +} + +func makeRepository(ctx context.Context, repoRepo models.IRepositoryRepo, user *models.User, name string) (*models.Repository, error) { + return repoRepo.Insert(ctx, &models.Repository{ + Name: name, + Description: utils.String("test"), + HEAD: "main", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + CreatorID: user.ID, + StorageNamespace: "mem://data", + }) +} + +// nolint +func makeCommit(ctx context.Context, commitRepo models.ICommitRepo, treeHash hash.Hash, msg string, parentsHash ...hash.Hash) (*models.Commit, error) { + commit := &models.Commit{ + RepositoryID: commitRepo.RepositoryID(), + Author: models.Signature{ + Name: "admin", + Email: "xxx@gg.com", + When: time.Now(), + }, + Committer: models.Signature{ + Name: "admin", + Email: "xxx@gg.com", + When: time.Now(), + }, + TreeHash: treeHash, + ParentHashes: parentsHash, + Message: msg, + } + hash, err := commit.GetHash() + if err != nil { + return nil, err + } + commit.Hash = hash + obj, err := commitRepo.Insert(ctx, commit) + if err != nil { + return nil, err + } + return obj, nil +} + +func makeBranch(ctx context.Context, branchRepo models.IBranchRepo, user *models.User, name string, repoID uuid.UUID, commitHash hash.Hash) (*models.Branches, error) { + branch := &models.Branches{ + RepositoryID: repoID, + CommitHash: commitHash, + Name: name, + Description: utils.String("test"), + CreatorID: user.ID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + return branchRepo.Insert(ctx, branch) +} + +func makeWip(ctx context.Context, wipRepo models.IWipRepo, creatorID, repoID, branchID uuid.UUID, parentHash, curHash hash.Hash) (*models.WorkingInProcess, error) { + wip := &models.WorkingInProcess{ + CurrentTree: curHash, + BaseCommit: parentHash, + RefID: branchID, + RepositoryID: repoID, + CreatorID: creatorID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + return wipRepo.Insert(ctx, wip) +} + +func rmWip(ctx context.Context, wipRepo models.IWipRepo, wipID uuid.UUID) error { + _, err := wipRepo.Delete(ctx, models.NewDeleteWipParams().SetID(wipID)) + return err +} + +func makeRoot(ctx context.Context, objRepo models.IFileTreeRepo, treeEntry models.TreeEntry, testData string) (*models.TreeNode, error) { + lines := strings.Split(testData, "\n") + treeOp, err := NewWorkTree(ctx, objRepo, treeEntry) + if err != nil { + return nil, err + } + for _, line := range lines { + if len(strings.TrimSpace(line)) == 0 { + continue + } + commitData := strings.Split(strings.TrimSpace(line), "|") + fullPath := strings.TrimSpace(commitData[1]) + fileHash := strings.TrimSpace(commitData[2]) + blob := &models.Blob{ + Hash: hash.Hash(fileHash), + RepositoryID: objRepo.RepositoryID(), + Type: models.BlobObject, + Size: 10, + Properties: models.Property{Mode: filemode.Regular}, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + if commitData[0] == "1" { + err = treeOp.AddLeaf(ctx, fullPath, blob) + if err != nil { + return nil, err + } + } else if commitData[0] == "3" { + err = treeOp.ReplaceLeaf(ctx, fullPath, blob) + if err != nil { + return nil, err + } + } else { + //2 + err = treeOp.RemoveEntry(ctx, fullPath) + if err != nil { + return nil, err + } + } + + } + return treeOp.Root().TreeNode(), nil +} diff --git a/versionmgr/worktree.go b/versionmgr/worktree.go index 04fbd798..66c3b1e6 100644 --- a/versionmgr/worktree.go +++ b/versionmgr/worktree.go @@ -4,25 +4,16 @@ import ( "context" "errors" "fmt" - "io" "os" "path/filepath" "strings" "github.com/google/uuid" - - "github.com/jiaozifs/jiaozifs/utils/httputil" - - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" - - "github.com/jiaozifs/jiaozifs/block" - "github.com/jiaozifs/jiaozifs/utils/hash" - "github.com/jiaozifs/jiaozifs/utils/pathutil" - - "golang.org/x/exp/slices" - "github.com/jiaozifs/jiaozifs/models" "github.com/jiaozifs/jiaozifs/models/filemode" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" + "golang.org/x/exp/slices" ) var EmptyRoot = &models.TreeNode{ @@ -77,77 +68,6 @@ func (workTree *WorkTree) RepositoryID() uuid.UUID { return workTree.object.RepositoryID() } -// ReadBlob read blob content with range -func (workTree *WorkTree) ReadBlob(ctx context.Context, adapter block.Adapter, storageNamespace string, blob *models.Blob, rangeSpec *string) (io.ReadCloser, error) { - address := pathutil.PathOfHash(blob.CheckSum) - pointer := block.ObjectPointer{ - StorageNamespace: storageNamespace, - IdentifierType: block.IdentifierTypeRelative, - Identifier: address, - } - - // setup response - var reader io.ReadCloser - - // handle partial response if byte range supplied - if rangeSpec != nil { - rng, err := httputil.ParseRange(*rangeSpec, blob.Size) - if err != nil { - return nil, err - } - reader, err = adapter.GetRange(ctx, pointer, rng.StartOffset, rng.EndOffset) - if err != nil { - return nil, err - } - - } else { - var err error - reader, err = adapter.Get(ctx, pointer, blob.Size) - if err != nil { - return nil, err - } - } - return reader, nil -} - -// WriteBlob write blob content to storage -func (workTree *WorkTree) WriteBlob(ctx context.Context, adapter block.Adapter, storageNamespace string, body io.Reader, contentLength int64, properties models.Property) (*models.Blob, error) { - // handle the upload itself - hashReader := hash.NewHashingReader(body, hash.Md5) - tempf, err := os.CreateTemp("", "*") - if err != nil { - return nil, err - } - _, err = io.Copy(tempf, hashReader) - if err != nil { - return nil, err - } - - checkSum := hash.Hash(hashReader.Md5.Sum(nil)) - _, err = tempf.Seek(0, io.SeekStart) - if err != nil { - return nil, err - } - - defer func() { - name := tempf.Name() - _ = tempf.Close() - _ = os.RemoveAll(name) - }() - - address := pathutil.PathOfHash(checkSum) - err = adapter.Put(ctx, block.ObjectPointer{ - StorageNamespace: storageNamespace, - IdentifierType: block.IdentifierTypeRelative, - Identifier: address, - }, contentLength, tempf, block.PutOpts{}) - if err != nil { - return nil, err - } - - return models.NewBlob(properties, workTree.RepositoryID(), checkSum, hashReader.CopiedSize) -} - func (workTree *WorkTree) AppendDirectEntry(ctx context.Context, treeEntry models.TreeEntry) (*models.TreeNode, error) { chilren, err := workTree.root.Children() if err != nil { diff --git a/versionmgr/worktree_test.go b/versionmgr/worktree_test.go index fd7bc2a4..0b27474b 100644 --- a/versionmgr/worktree_test.go +++ b/versionmgr/worktree_test.go @@ -1,91 +1,48 @@ package versionmgr import ( - "bytes" "context" "errors" - "io" "testing" + "github.com/brianvoe/gofakeit/v6" "github.com/google/uuid" - - "github.com/jiaozifs/jiaozifs/utils/hash" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/jiaozifs/jiaozifs/block/mem" "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/stretchr/testify/require" ) -func TestTreeWriteBlob(t *testing.T) { - ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint - - repoID := uuid.New() - adapter := mem.New(ctx) - namespace := "mem://data" - objRepo := models.NewFileTree(db, repoID) - - workTree, err := NewWorkTree(ctx, objRepo, EmptyDirEntry) - require.NoError(t, err) - - binary := []byte("Build simple, secure, scalable systems with Go") - bLen := int64(len(binary)) - r := bytes.NewReader(binary) - blob, err := workTree.WriteBlob(ctx, adapter, namespace, r, bLen, models.DefaultLeafProperty()) - require.NoError(t, err) - assert.Equal(t, bLen, blob.Size) - assert.Equal(t, "99b91d4c517d0cded9506be9298b8d02", blob.Hash.Hex()) - assert.Equal(t, "f3b39786b86a96372589aa1166966643", blob.CheckSum.Hex()) - - reader, err := workTree.ReadBlob(ctx, adapter, namespace, blob, nil) - require.NoError(t, err) - content, err := io.ReadAll(reader) - require.NoError(t, err) - require.Equal(t, binary, content) -} - func TestWorkTreeTreeOp(t *testing.T) { ctx := context.Background() postgres, _, db := testhelper.SetupDatabase(ctx, t) defer postgres.Stop() //nolint repoID := uuid.New() - adapter := mem.New(ctx) - namespace := "mem://data" objRepo := models.NewFileTree(db, repoID) workTree, err := NewWorkTree(ctx, objRepo, EmptyDirEntry) require.NoError(t, err) - binary := []byte("Build simple, secure, scalable systems with Go") - bLen := int64(len(binary)) - r := bytes.NewReader(binary) - blob, err := workTree.WriteBlob(ctx, adapter, namespace, r, bLen, models.DefaultLeafProperty()) - require.NoError(t, err) + blob := &models.Blob{} + require.NoError(t, gofakeit.Struct(blob)) + blob.Type = models.BlobObject + blob.RepositoryID = repoID err = workTree.AddLeaf(ctx, "a/b/c.txt", blob) require.NoError(t, err) - require.Equal(t, "faf499deee898c13e4ae4a2e6c4230fb", hash.Hash(workTree.Root().Hash()).Hex()) //add again expect get an error err = workTree.AddLeaf(ctx, "a/b/c.txt", blob) require.True(t, errors.Is(err, ErrEntryExit)) //update path - binary = []byte(`“At the time, no single team member knew Go, but within a month, everyone was writing in Go and we were building out the endpoints. ”`) - bLen = int64(len(binary)) - r = bytes.NewReader(binary) - blob, err = workTree.WriteBlob(ctx, adapter, namespace, r, bLen, models.DefaultLeafProperty()) - require.NoError(t, err) - + blob = &models.Blob{} + require.NoError(t, gofakeit.Struct(blob)) + blob.Type = models.BlobObject + blob.RepositoryID = repoID err = workTree.ReplaceLeaf(ctx, "a/b/c.txt", blob) require.NoError(t, err) - require.Equal(t, "d08bf786f0b4375dd6edd880859dc47a", hash.Hash(workTree.Root().Hash()).Hex()) { //find blob @@ -98,7 +55,6 @@ func TestWorkTreeTreeOp(t *testing.T) { //add another branch err = workTree.AddLeaf(ctx, "a/b/d.txt", blob) require.NoError(t, err) - require.Equal(t, "b37d803cc5431587ef6f6e4d3aa8ada4", hash.Hash(workTree.Root().Hash()).Hex()) } @@ -137,7 +93,6 @@ func TestWorkTreeTreeOp(t *testing.T) { err = workTree.RemoveEntry(ctx, "a/b/c.txt") require.NoError(t, err) - require.Equal(t, "291af4419b76a09b60aa0cf911c72d06", hash.Hash(workTree.Root().Hash()).Hex()) err = workTree.RemoveEntry(ctx, "a/b/d.txt") require.NoError(t, err) @@ -150,34 +105,26 @@ func TestRemoveEntry(t *testing.T) { defer postgres.Stop() //nolint repoID := uuid.New() - adapter := mem.New(ctx) - namespace := "mem://data" objRepo := models.NewFileTree(db, repoID) workTree, err := NewWorkTree(ctx, objRepo, EmptyDirEntry) require.NoError(t, err) - binary := []byte("Build simple, secure, scalable systems with Go") - bLen := int64(len(binary)) - r := bytes.NewReader(binary) - blob, err := workTree.WriteBlob(ctx, adapter, namespace, r, bLen, models.DefaultLeafProperty()) - require.NoError(t, err) - + blob := &models.Blob{} + require.NoError(t, gofakeit.Struct(blob)) + blob.Type = models.BlobObject + blob.RepositoryID = repoID err = workTree.AddLeaf(ctx, "a/b/c.txt", blob) require.NoError(t, err) - require.Equal(t, "faf499deee898c13e4ae4a2e6c4230fb", hash.Hash(workTree.Root().Hash()).Hex()) //update path - binary = []byte(`“At the time, no single team member knew Go, but within a month, everyone was writing in Go and we were building out the endpoints. ”`) - bLen = int64(len(binary)) - r = bytes.NewReader(binary) - blob, err = workTree.WriteBlob(ctx, adapter, namespace, r, bLen, models.DefaultLeafProperty()) - require.NoError(t, err) - + blob = &models.Blob{} + require.NoError(t, gofakeit.Struct(blob)) + blob.Type = models.BlobObject + blob.RepositoryID = repoID //add another branch err = workTree.AddLeaf(ctx, "a/b/d.txt", blob) require.NoError(t, err) - require.Equal(t, "77e60b4b1f28022818a3b97dfe064a3e", hash.Hash(workTree.Root().Hash()).Hex()) err = workTree.RemoveEntry(ctx, "a/b") require.NoError(t, err) From 81bc5fe9d9acce57126018e3e76fb8920f2c9f1f Mon Sep 17 00:00:00 2001 From: zjy Date: Mon, 25 Dec 2023 00:36:02 +0800 Subject: [PATCH 116/210] chore: rebuild import by goimports --- controller/branch_ctl.go | 2 -- integrationtest/branch_test.go | 2 +- models/branch_test.go | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/controller/branch_ctl.go b/controller/branch_ctl.go index 47d89c09..71aaddf1 100644 --- a/controller/branch_ctl.go +++ b/controller/branch_ctl.go @@ -9,8 +9,6 @@ import ( "strings" "time" - - "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/auth" "github.com/jiaozifs/jiaozifs/models" diff --git a/integrationtest/branch_test.go b/integrationtest/branch_test.go index 1c7fbcf2..3b9dc86d 100644 --- a/integrationtest/branch_test.go +++ b/integrationtest/branch_test.go @@ -5,10 +5,10 @@ import ( "net/http" "strings" - "github.com/jiaozifs/jiaozifs/utils" "github.com/jiaozifs/jiaozifs/api" apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" "github.com/jiaozifs/jiaozifs/controller" + "github.com/jiaozifs/jiaozifs/utils" "github.com/smartystreets/goconvey/convey" ) diff --git a/models/branch_test.go b/models/branch_test.go index fbbe3846..25983ca5 100644 --- a/models/branch_test.go +++ b/models/branch_test.go @@ -4,7 +4,6 @@ import ( "context" "testing" - "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" "github.com/google/uuid" From 68e76e4fe15687abf0e2942f1142802fbb03c3e8 Mon Sep 17 00:00:00 2001 From: brown Date: Mon, 25 Dec 2023 00:42:37 +0800 Subject: [PATCH 117/210] chore: make gen api --- api/jiaozifs.gen.go | 132 ++++++++++++++++++++++---------------------- 1 file changed, 67 insertions(+), 65 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 5869df9c..25cf239e 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -6551,71 +6551,73 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xc63PbNrb/VzC8/ZDcq5cfzdyq0+nEjtN410k9ttN8iL0aiDyUkJAAC4CW1Yz/9x0A", - "fBOkKEW25e5+8VgkHuf5wzkHAL85LgsjRoFK4Yy/OcKdQ4j1v69jOQcqiYslYfSKfQWqHkecRcAlAd1I", - "po89EC4nkWrqjB2M/vHpCumXSM6xRC6LAw9NAcUCPCQZwvnogDj8GYOQwuk5chmBM3aE5ITOnPuemWEC", - "dxHh2IxenewjJXfoJGLuHBGKBLiMemoon/EQS2fsECpfHeZjEyphBty5v+85ambCwXPGnxNebrJ2bPoF", - "XKloOOKYuvM698csDIl8h4V+VyP9mAOW4L2W6m1GjYcl9CUJwcat7sL46ZtSlzgmnq31m6IcLAR0HOYD", - "DsHa/wIiJohkfNlxpI+Rtx7HFRWcvnEqs/aKQk5ILYqpKOXi/M1q1O0TiZXVSZvkIFjMXdurCvnUUJc0", - "bybhjAhZnz7CM0Iz0n7g4Dtj53+GuYMOE+8cnuctNQUiDoz7EgmhWNU7seb7jDzMOV7WmCmQk89h4+l4", - "jukM6vy8dlNegMahM/6819vvHdzU/bDnHGEBjW50jqX9xRVr6FPhRA/QS+mxsqBtzMJCLOeMrxLoJZlR", - "LGMO+VAS1uy1PlQ0yus98Blc4VnDSyHwDBoEzYFqT4OyNdVBuWQ4GwDFFYdmhX8viiRYcaUa1eAkUWlR", - "UQWR5QIq0FiRzDqQY1rmJNRN7Chg7tdLyTgcM+qTWX19m6oWSEjG8QyQq1uhmAcIqMs88NAXoX107eWh", - "Afcr0tStbLydsRmhOdFlti6OXh/XWVFP0YIEAeIQYkIRUDwNwEOMot8+niLio2sH7iRwioNrZ4DQlYoe", - "GA2WaMH4V3FNF0TOEaYobaUjCSSA3xIXBtdKEAncOIKEUUB8AsoE0/YFVnJJ+DgIptj9OgkUT5MATyGo", - "U68fq+AlCrALiuZKv5gHA2f18DG3DG7iFsyX6OPFmZqE+T5wFS9xoX7GApDPONJDWGcxg7uMfSUwUUuR", - "qM9i3iL9NovFlGmBitic3ho+b6bzMQnAm4Q5rJQnTF6oaTwiogAvE2a4QIs5Q6q/eqJH+xlh5MdBgARQ", - "CdQFEzwSgThQDzh415RQ9O7q/RnC1EMhXip/kMqSMAoI/apDS5TLUg+LQpBz5l3TZqlZVRJxEhYU0kkD", - "LJb2weqDzAidIRbLwUpAy2m0ark0sc1Tf9f/XUpsooSyp7pzcL8K5TEWpSvpApUT86LKkxkXheARjKSB", - "29oQIUjsYYlXrYdmsI8C+Pu0h+qtEX97MX/PiZrCCfViEjIPyisYofJg3zqSIH/BZLqURo7rphuZ3BOS", - "EgISMRq+m5VZktP4m4M9jyjZ4OC8nKA1uHE+3nkp7CzbxhyLSci4RQEf4E6iSHk2EQjfYhIoIM+5njIW", - "ANbxaYjvJhHwSWQFiPf4joQ4QDQOp8AR8xFQyQkIFAHXMyhpEEpCZaIjmx4o3MkJ830Bsj6+TlwzqOOg", - "xr5VwAKIpjzYzLYQVVc4zwi9xUEMAvkspp4yQzVm2q2d5oopZGKuCCunosykzSwuwL9KnDRd/6Ym0O85", - "CxIpFnV845pQ17YKtoUpT57LvgPsPUiSu5WcNclLNZGbpqeXIONIgbQll1Jam0QcfDEJiRCKjpphSh6D", - "iqCUGar2SLdHmANK+gys/pmuKGkg1wbSxZhPIWBKbWpyhBJJcED+0jEXZXJSfHJjk2VdDlliVBPDSYhJ", - "UNIT6Cfr6PvT3FSsNlB1ouWTZE49kk2TKnM4odLmR41Jz6l4Q3jhTUFBzXF6bWZjYW2O3O5n1jEF8FPq", - "M4tVrg8KbsxVKqV0fEo37nh6Xl5xo9tDWx/obi4BFhsQlffqSFGs9bPOFCpUpp0StaxlyvhNgzIvYEaE", - "bFLqGkKLsBALxjUuh4SeAZ2p2Or/t8tGYR4bR38AF4TRC71S1tnBEZncmiZ1yOQxVXJHaQOriiUIWRyi", - "1qRx+IizGcdh8/AV1vN2RaptTH8ikaWUgAXklazHL0cfGxdV6Lcb5ehsMS3G5tZQfpMgoKIUtRyCG3Mi", - "l5dqtTQ6mWJB3AmOTc6hl1GN7upxPupcysikWzqtS5uTPGVXq6mWi6aaUxzoVhMBomxaOCL/BJ2gf1nI", - "SbY/MwXMgb9NOTPJfk6OflulR7FEEowoG/YXgtlfxBfo3dXVOXp9fqpyUOICFZAX0p3XEXbngPYHI6fn", - "6KRYDyzGw+FisRhg/XrA+GyY9BXDs9Pjkw+XJ/39wWgwl2GgMxYiAyhOaubLvM7ZG4wGI9WSRUBxRJyx", - "c6AfmZRK62GopDXUoY52HGYq8Mp9dO5z6jljU9FyjE+CkEfMW5rgSyfBBk2iINkRG+qyW7ZnZqvn5+i4", - "HTxswcF7001ETMlRjbo/Gq1FfFvYZ9sL1DNWalix64IQfhyYIonTc+aAPeCaoEuQ/WNjzKWJ4Q6HUdBo", - "2r/gqevB3v7Bj69+RudYzn8Z/ozeSRn9ToOlxTEVWYejPVvNAOvarwpF0R84IJ7m5oRzpjHgcH9kCaoZ", - "QyGmy3yPUnPt42SxKbc+TRhAl8BvgaNk7AI0OOPPNz1HxGGIVXTmRMAV3CCcSUzimVB61yBwo/pmtsti", - "2Wq86r3dCtr0pHrtpszsUjJcWsRkfGH4jS0o8PvhN56tF/dm2gDMclAW3Bv93JRV6uI7rFNs5kFmPA/l", - "0gyWnQRpGh3UG71lfEo8D6hpYZn6A5NvWUy9dWRfkqQhGhkWBui9SQyT38LU5imTiIOMOUUYpTMiUHoZ", - "FCSf9HFu7nvODCwW+RvITKoR5jgEqaHgc43oZQSIUM+cBiiWaXzOQrQg0dDUMoYSz3ooMSWU1Tf0Ovln", - "DHyZL5NJHS1HUpUe9zriXVpMub/vVWk9WkpAHNNZiVC9wZDCmC4J/jLq7432D1LqDA7m5F3oPdMiPRGW", - "yhGcsfMvM8CLF9fX3v/21Z/er+jXl//38gcL3N2sBfvMlSD7QnLAYRmFs6BnSijmVmDt2f0gnaoE9sfm", - "YT/NCaxTtVRKT5INzLxXfZU8w0L23zPPbPG0NlbN90evHksyEeaS4AA9pITS/hfp7vt3m9KDSP1gtG/Z", - "BwSPcCUZvV0TcegLMqPg6a0Wn3Fdw2IpdhSEdsbcrFTdPm9HFG6Gd4WCfoa1e6PGhvpsUjLe3isbsxqJ", - "wUNaVQpR0SWWRPhE18w3hfIZyLqB2cB5npROy+j8DrD3X3h+InhuMCRiDsE9CxztgnhIp4//ibD3t4Sf", - "lig+Td30SQzgJlqsAJbe8UTER1V7t4FWBZGIMTK9T5r4qA7zWzGkpkXrOHmasO5glXM6GQYq6DGbgX4D", - "/HHwk82E75iQQ4AluYXV0yUMd5/rpteQZX6MAlZYN7pVSr4ntuo5YRxIovBlqFr30x3vprJLgYbKaQUa", - "LBFGKt8JAPkkAL3FHGuO0GJO3DkKYyHR1ByQ8dB1Oti1MygeLmghtkNZZm9rZZniuY7m+DwsHKc4tC0/", - "trx+o2LAZjltzIMq2llCxnOuD3noQw5v9aGjNQOnGsj0nLv+bcZFH+7cIPagP9W2rBxEFxU0OmxYU7go", - "I0vHsow+K2XS9AI0bVN5XZRlqxqUkDKVp3rYWgNYKYWt+EJhFosrqFh5V4RZocUiyZ1f+1qWh8r+8+bF", - "9DZl16a5L1fNtfeu6XJma3ZnrKROTs1Q2uEpyclWo9RRmqfZzG4LcctNl5qqITbFvQ1Lqoe26NfcxtBh", - "b3vp9GrrZeuEmywRTvVnHkB76fTx1bI9ME6vmNSBeJpdPnmmOlXo3a7Q54ve5oRAZngPgdyVO1idcHvv", - "QWevHJDXIkg0nOLQeitBd4sd/dTS9JhRPyCuFOgTkXN0hblCiscz9JIk7LbeaQEyWrSi3BkRCczpU+wP", - "DEf61l0jJKFAv362uKTIR9NcmM8UmlaYlDm93GxRv4E0J6DEKS1Foa0Fbg7+i7x68xIlxy3aV9qHW1k7", - "3eJMDnrVb3Fac59UbvW1LHmDCH32Sclq24kwh+G3KRYwB+zdrzajN8T3V1mPiMAlPnGR4qKHiK+rGdnT", - "ZCc9vdWg5MyYbC/UPbVtmRu9HWzLWA/ylJi2nS79aEuXkvJyVm6GhhCtQBhwoG4JE9M7EM+kzGwZLDXh", - "LTuINqJWdD0xZqzQdbcco9c4u0H2Hso2JvXGoIqACePLynbli3cnr9+8bAb/9WjY4Z3TRwGS/CJEZyx5", - "9KpLt6p0PdYq2q22i+eILhoSBMg4anP6ws2kB4zSC7NYrCM7/aupRebm0Vp7l43rCXEBxTS/xNh2XjPb", - "wyzTU1E/o0l2pC86D3ly4aL58GZ6JeOh6qXVWx/NG1PVyFh1MoSuzIaPsIeS3WbUL2wQod04Yqt0gYoM", - "2U+RpiqLWHvimqcXv/uF89HgKWE7j4GuF6VS9Sp41aC1K9XtKjG2fKOlRPXgGwy1aR6gUPUAOqaw2BkV", - "J/WjVRsYxt3U37YVKLuD+IDrTzaHRbCX+Yl5fezON2himn+/9Ewc8t11aULNEQWFuSy5CGzuZAX6Uxgz", - "8PpEX4bnbdiXJgfrYGDXfYqInXPwyd3TJ7nreVZuxoVC4Y6gp/4ERprtpAHlo9RvdPhYuP7Y5L5/ZBcb", - "H8x7y9dAbWeyK5cx20KGJDVNu2DqIctVUVvAp1K6zQ6LfNIfadjklMiCRE9rjxxCdgv6C02EzpQ5Rpzp", - "UDGXkiKybbuzmf2tmIca3mIUFpKf/GxIJzGu9uat1ps2SlNrRfZuhfWtbWRaTWrv8U0KJd9JeILSxo+2", - "2xedzuqa6K2DLbah3tDVleTWiuInEh0nrVZv02zbgnr1c+xy/jepzdsMMRH0DmJcRtsmWLcLVbRmH8g/", - "LvrszrQ/KmhrORnQbsWBZG8nzL7UaSMtFLOdOQ7VsFIkfKQBnY4yExKQ/sylSuefIrj7nnXD8GTxb8nq", - "Z0k6rCBB8q3kxhx0C4FjJ+D9ZBSxPuruQK64IFEpSYw401cDlMlVqgHPCHTLCdy34gdPPt+oyYofXzFP", - "Sh9Y+XyjvN4Ysw1nCiV+Y+/Ui5j5gkz+NZPxcBgwFwdzJuT44PCnvYMhjsjwds+po+nKAbOuN/f/DgAA", - "///DhHZ9ml8AAA==", + "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", } // GetSwagger returns the content of the embedded swagger specification file From b205144e090ccf622264b2120a9897abbd17944d Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Sun, 24 Dec 2023 15:40:56 +0800 Subject: [PATCH 118/210] feat: change swagger key --- api/jiaozifs.gen.go | 254 +++++++------ api/swagger.yml | 359 ++++++++++-------- config/blockstore.go | 84 ++-- config/default.go | 8 +- controller/branch_ctl.go | 20 +- controller/repository_ctl.go | 26 +- controller/user_ctl.go | 14 +- integrationtest/commit_test.go | 16 +- integrationtest/helper_test.go | 2 +- integrationtest/repo_test.go | 6 +- models/branch.go | 36 +- models/branch_test.go | 4 +- models/commit.go | 26 +- models/merge_request.go | 34 +- .../migrations/20210505110026_init_project.go | 2 +- models/repository.go | 30 +- models/tag.go | 20 +- models/tree.go | 40 +- models/user.go | 20 +- models/wip.go | 18 +- versionmgr/work_repo.go | 14 +- versionmgr/work_repo_test.go | 38 +- 22 files changed, 558 insertions(+), 513 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 25cf239e..f347301d 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -67,14 +67,14 @@ type AuthenticationToken struct { // Branch defines model for Branch. type Branch struct { - CommitHash string `json:"CommitHash"` - CreatedAt time.Time `json:"CreatedAt"` - CreatorID openapi_types.UUID `json:"CreatorID"` - Description *string `json:"Description,omitempty"` - ID openapi_types.UUID `json:"ID"` - Name string `json:"Name"` - RepositoryID openapi_types.UUID `json:"RepositoryID"` - UpdatedAt time.Time `json:"UpdatedAt"` + CommitHash string `json:"commit_hash"` + CreatedAt time.Time `json:"created_at"` + CreatorId openapi_types.UUID `json:"creator_id"` + Description *string `json:"description,omitempty"` + Id openapi_types.UUID `json:"id"` + Name string `json:"name"` + RepositoryId openapi_types.UUID `json:"repository_id"` + UpdatedAt time.Time `json:"updated_at"` } // BranchCreation defines model for BranchCreation. @@ -91,10 +91,10 @@ type BranchList struct { // Change defines model for Change. type Change struct { - Action ChangeAction `json:"Action"` - BaseHash *string `json:"BaseHash,omitempty"` - Path string `json:"Path"` - ToHash *string `json:"ToHash,omitempty"` + Action ChangeAction `json:"action"` + BaseHash *string `json:"base_hash,omitempty"` + Path string `json:"path"` + ToHash *string `json:"to_hash,omitempty"` } // ChangeAction defines model for Change.Action. @@ -102,24 +102,24 @@ type ChangeAction int // Commit defines model for Commit. type Commit struct { - Author Signature `json:"Author"` - Committer Signature `json:"Committer"` - CreatedAt time.Time `json:"CreatedAt"` - Hash string `json:"Hash"` - MergeTag string `json:"MergeTag"` - Message string `json:"Message"` - ParentHashes []string `json:"ParentHashes"` - RepositoryID openapi_types.UUID `json:"RepositoryID"` - TreeHash string `json:"TreeHash"` - UpdatedAt time.Time `json:"UpdatedAt"` + Author Signature `json:"author"` + Committer Signature `json:"committer"` + CreatedAt time.Time `json:"created_at"` + Hash string `json:"hash"` + MergeTag string `json:"merge_tag"` + Message string `json:"message"` + ParentHashes []string `json:"parent_hashes"` + RepositoryId openapi_types.UUID `json:"repository_id"` + TreeHash string `json:"tree_hash"` + UpdatedAt time.Time `json:"updated_at"` } // CreateRepository defines model for CreateRepository. type CreateRepository struct { - // BlockStoreConfig block storage config url encoded json - BlockStoreConfig *string `json:"BlockStoreConfig,omitempty"` - Description *string `json:"Description,omitempty"` - Name string `json:"Name"` + // BlockstoreConfig block storage config url encoded json + BlockstoreConfig *string `json:"blockstore_config,omitempty"` + Description *string `json:"description,omitempty"` + Name string `json:"name"` } // LoginConfig defines model for LoginConfig. @@ -190,13 +190,17 @@ type RefType string // Repository defines model for Repository. type Repository struct { - CreatedAt time.Time `json:"CreatedAt"` - CreatorID openapi_types.UUID `json:"CreatorID"` - Description *string `json:"Description,omitempty"` - Head string `json:"Head"` - ID openapi_types.UUID `json:"ID"` - Name string `json:"Name"` - UpdatedAt time.Time `json:"UpdatedAt"` + CreatedAt time.Time `json:"created_at"` + CreatorId openapi_types.UUID `json:"creator_id"` + Description *string `json:"description,omitempty"` + Head string `json:"head"` + Id openapi_types.UUID `json:"id"` + Name string `json:"name"` + OwnerId openapi_types.UUID `json:"owner_id"` + StorageAdapterParams *string `json:"storage_adapter_params,omitempty"` + StorageNamespace *string `json:"storage_namespace,omitempty"` + UpdatedAt time.Time `json:"updated_at"` + UsePublicStorage bool `json:"use_public_storage"` } // RepositoryList defines model for RepositoryList. @@ -218,40 +222,41 @@ type SetupStateState string // Signature defines model for Signature. type Signature struct { - Email openapi_types.Email `json:"Email"` - Name string `json:"Name"` - When time.Time `json:"When"` + Email openapi_types.Email `json:"email"` + Name string `json:"name"` + When time.Time `json:"when"` } // TreeEntry defines model for TreeEntry. type TreeEntry struct { - Hash *string `json:"Hash,omitempty"` - IsDir *bool `json:"IsDir,omitempty"` - Name *string `json:"Name,omitempty"` + Hash string `json:"hash"` + IsDir bool `json:"is_dir"` + Name string `json:"name"` } // UpdateRepository defines model for UpdateRepository. type UpdateRepository struct { - Description *string `json:"Description,omitempty"` + Description *string `json:"description,omitempty"` } // UserInfo defines model for UserInfo. type UserInfo struct { - CreatedAt *time.Time `json:"createdAt,omitempty"` - CurrentSignInAt *time.Time `json:"currentSignInAt,omitempty"` - CurrentSignInIP *string `json:"currentSignInIP,omitempty"` + CreatedAt time.Time `json:"created_at"` + CurrentSignInAt *time.Time `json:"current_sign_in_at,omitempty"` + CurrentSignInIp *string `json:"current_sign_in_ip,omitempty"` Email openapi_types.Email `json:"email"` - LastSignInAt *time.Time `json:"lastSignInAt,omitempty"` - LastSignInIP *string `json:"lastSignInIP,omitempty"` - UpdateAt *time.Time `json:"updateAt,omitempty"` - Username string `json:"username"` + Id openapi_types.UUID `json:"id"` + LastSignInAt *time.Time `json:"last_sign_in_at,omitempty"` + LastSignInIp *string `json:"last_sign_in_ip,omitempty"` + Name string `json:"name"` + UpdatedAt time.Time `json:"updated_at"` } // UserRegisterInfo defines model for UserRegisterInfo. type UserRegisterInfo struct { Email openapi_types.Email `json:"email"` + Name string `json:"name"` Password string `json:"password"` - Username string `json:"username"` } // VersionResult defines model for VersionResult. @@ -266,15 +271,15 @@ type VersionResult struct { // Wip defines model for Wip. type Wip struct { - BaseCommit *string `json:"BaseCommit,omitempty"` - CreatedAt *time.Time `json:"CreatedAt,omitempty"` - CreatorID *openapi_types.UUID `json:"CreatorID,omitempty"` - CurrentTree *string `json:"CurrentTree,omitempty"` - ID *openapi_types.UUID `json:"ID,omitempty"` - Name *string `json:"Name,omitempty"` - RepositoryID *openapi_types.UUID `json:"RepositoryID,omitempty"` - State *int `json:"State,omitempty"` - UpdatedAt *time.Time `json:"UpdatedAt,omitempty"` + BaseCommit string `json:"base_commit"` + CreatedAt time.Time `json:"created_at"` + CreatorId openapi_types.UUID `json:"creator_id"` + CurrentTree string `json:"current_tree"` + Id openapi_types.UUID `json:"id"` + RefId openapi_types.UUID `json:"ref_id"` + RepositoryId openapi_types.UUID `json:"repository_id"` + State int `json:"state"` + UpdatedAt time.Time `json:"updated_at"` } // PaginationAmount defines model for PaginationAmount. @@ -6551,73 +6556,74 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 9bd45375..2d2a2856 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -135,33 +135,33 @@ components: Branch: type: object required: - - ID - - RepositoryID - - CommitHash - - Name - - CreatorID - - CreatedAt - - UpdatedAt + - id + - repository_id + - commit_hash + - name + - creator_id + - created_at + - updated_at properties: - ID : + id : type: string format: uuid - RepositoryID: + repository_id: type: string format: uuid - CommitHash: + commit_hash: type: string - Name : + name : type: string - Description : + description : type: string - CreatorID: + creator_id: type: string format: uuid - CreatedAt: + created_at: type: string format: date-time - UpdatedAt: + updated_at: type: string format: date-time BranchList: @@ -179,20 +179,20 @@ components: CreateRepository: type: object required: - - Name + - name properties: - Description: + description: type: string - Name: + name: type: string - BlockStoreConfig: + blockstore_config: description: block storage config url encoded json type: string UpdateRepository: type: object properties: - Description: + description: type: string RepositoryList: type: object @@ -209,204 +209,229 @@ components: Repository: type: object required: - - ID - - Name - - Head - - CreatorID - - CreatedAt - - UpdatedAt + - id + - name + - owner_id + - head + - use_public_storage + - creator_id + - created_at + - updated_at properties: - ID: + id: type: string format: uuid - Name: + name: type: string - Head: + owner_id: type: string - Description: + format: uuid + head: type: string - CreatorID: + use_public_storage: + type: boolean + storage_adapter_params: + type: string + storage_namespace: + type: string + description: + type: string + creator_id: type: string format: uuid - CreatedAt: + created_at: type: string format: date-time - UpdatedAt: + updated_at: type: string format: date-time Blob: type: object required: - - Hash - - Type - - CheckSum - - RepositoryID - - Properties - - Size - - CreatedAt - - UpdatedAt + - hash + - repository_id + - check_sum + - type + - size + - properties + - created_at + - updated_at properties: - Hash: + hash: type: string - RepositoryID: + repository_id: type: string format: uuid - CheckSum: + check_sum: type: string - Type: + type: type: integer format: int8 - Properties: + size: + type: integer + format: int64 + properties: type: object additionalProperties: type: string - Size: - type: integer - format: int64 - CreatedAt: + created_at: type: string format: date-time - UpdatedAt: + updated_at: type: string format: date-time Signature: type: object required: - - Name - - Email - - When + - name + - email + - when properties: - Name: + name: type: string - Email: + email: type: string format: email - When: + when: type: string format: date-time Commit: type: object required: - - Hash - - Type - - RepositoryID - - Author - - Committer - - MergeTag - - Message - - TreeHash - - ParentHashes - - CreatedAt - - UpdatedAt + - hash + - repository_id + - author + - committer + - merge_tag + - message + - tree_hash + - parent_hashes + - created_at + - updated_at properties: - Hash: + hash: type: string - Author: - $ref: "#/components/schemas/Signature" - Committer: - $ref: "#/components/schemas/Signature" - RepositoryID: + repository_id: type: string format: uuid - MergeTag: + author: + $ref: "#/components/schemas/Signature" + committer: + $ref: "#/components/schemas/Signature" + merge_tag: type: string - Message: + message: type: string - TreeHash: + tree_hash: type: string - ParentHashes: + parent_hashes: type: array items: type: string - CreatedAt: + created_at: type: string format: date-time - UpdatedAt: + updated_at: type: string format: date-time TreeEntry: type: object + required: + - name + - hash + - is_dir properties: - Name: + name: type: string - Hash: + hash: type: string - IsDir: + is_dir: type: boolean TreeNode: type: object required: - - Hash - - Type - - RepositoryID - - Properties - - SubObjects - - CreatedAt - - UpdatedAt + - hash + - repository_id + - type + - properties + - sub_objects + - created_at + - updated_at properties: - Hash: + hash: type: string - Type: - type: integer - format: int8 - RepositoryID: + repository_id: type: string format: uuid - Properties: + type: + type: integer + format: int8 + properties: type: object additionalProperties: type: string - SubObjects: + sub_objects: type: array items: $ref: "#/components/schemas/TreeEntry" - CreatedAt: + created_at: type: string format: date-time - UpdatedAt: + updated_at: type: string format: date-time Wip: type: object + required: + - id + - current_tree + - base_commit + - repository_id + - ref_id + - state + - creator_id + - created_at + - updated_at properties: - ID: + id: type: string format: uuid - Name: + current_tree: type: string - CurrentTree: + base_commit: type: string - BaseCommit: + repository_id: type: string - RepositoryID: + format: uuid + ref_id: type: string format: uuid - State: + state: type: integer format: int - CreatorID: + creator_id: type: string format: uuid - CreatedAt: + created_at: type: string format: date-time - UpdatedAt: + updated_at: type: string format: date-time Change: type: object required: - - Path - - Action + - path + - action properties: - Path: + path: type: string - Action: + action: type: integer enum: [1,2,3] - BaseHash: + base_hash: type: string - ToHash: + to_hash: type: string UserUpdate: type: object @@ -425,40 +450,46 @@ components: UserInfo: type: object required: - - username + - id + - name - email + - created_at + - updated_at properties: - username: + id: + type: string + format: uuid + name: type: string email: type: string format: email - currentSignInAt: + current_sign_in_at: type: string format: date-time - lastSignInAt: + last_sign_in_at: type: string format: date-time - currentSignInIP: + current_sign_in_ip: type: string format: ipv4 - lastSignInIP: + last_sign_in_ip: type: string format: ipv4 - createdAt: + created_at: type: string format: date-time - updateAt: + updated_at: type: string format: date-time UserRegisterInfo: type: object required: - - username + - name - email - password properties: - username: + name: type: string password: type: string @@ -537,88 +568,98 @@ components: $ref: "#/components/schemas/ObjectStats" BlockStoreConfig: type: object + required: + - type properties: - Type: + type: type: string description: type of support storage type enum: [ "local", "gs", "azure", "s3" ] - DefaultNamespacePrefix: + default_namespace_prefix: type: string - Local: + local: type: object required: - - Path + - path properties: - Path: + path: type: string - Azure: + azure: type: object required: - - StorageAccount - - StorageAccessKey - - TryTimeout + - storage_access_key + - storage_account + - try_timeout properties: - StorageAccessKey: + storage_access_key: type: string - StorageAccount: + storage_account: type: string - TryTimeout: + try_timeout: type: integer format: int64 - GS: + gs: type: object required: - - CredentialsJSON - - S3Endpoint + - credentials_json + - s3_endpoint properties: - CredentialsJSON: + credentials_json: type: string - S3Endpoint: + s3_endpoint: type: string - S3: + s3: type: object required: - - Credentials - - DiscoverBucketRegion + - credentials + - discover_bucket_region - Region - - Endpoint + - endpoint properties: - Credentials: + credentials: $ref: '#/components/schemas/Credential' - WebIdentity: + web_identity: $ref: '#/components/schemas/WebIdentity' - DiscoverBucketRegion: + discover_bucket_region: type: boolean - Endpoint: + endpoint: type: string - ForcePathStyle: + force_path_style: type: boolean - Region: + region: type: string WebIdentity: + type: object + required: + - session_duration + - session_expiry_window properties: - SessionDuration: + session_duration: type: integer format: int64 - SessionExpiryWindow: + session_expiry_window: type: integer format: int64 S3AuthInfo: type: object description: S3AuthInfo holds S3-style authentication. + required: + - credentials properties: - Credentials: + credentials: $ref: '#/components/schemas/Credential' - CredentialsFile: - type: string Credential: type: object + required: + - access_key_id + - secret_access_key + - session_token properties: - AccessKeyID: + access_key_id: $ref: '#/components/schemas/SecureString' - SecretAccessKey: + secret_access_key: $ref: '#/components/schemas/SecureString' - SessionToken: + session_token: $ref: '#/components/schemas/SecureString' SecureString: type: string diff --git a/config/blockstore.go b/config/blockstore.go index ee216c8a..0dd677f9 100644 --- a/config/blockstore.go +++ b/config/blockstore.go @@ -9,53 +9,53 @@ import ( ) type BlockStoreConfig struct { - Type string `mapstructure:"type" validate:"required"` - DefaultNamespacePrefix *string `mapstructure:"default_namespace_prefix"` + Type string `mapstructure:"type" validate:"required" json:"type"` + DefaultNamespacePrefix *string `mapstructure:"default_namespace_prefix" json:"default_namespace_prefix"` Local *struct { - Path string `mapstructure:"path"` - ImportEnabled bool `mapstructure:"import_enabled"` - ImportHidden bool `mapstructure:"import_hidden"` - AllowedExternalPrefixes []string `mapstructure:"allowed_external_prefixes"` - } `mapstructure:"local"` + Path string `mapstructure:"path" json:"path"` + ImportEnabled bool `mapstructure:"import_enabled" json:"import_enabled"` + ImportHidden bool `mapstructure:"import_hidden" json:"import_hidden"` + AllowedExternalPrefixes []string `mapstructure:"allowed_external_prefixes" json:"allowed_external_prefixes"` + } `mapstructure:"local" json:"local"` S3 *struct { S3AuthInfo `mapstructure:",squash"` - Region string `mapstructure:"region"` - Endpoint string `mapstructure:"endpoint"` - MaxRetries int `mapstructure:"max_retries"` - ForcePathStyle bool `mapstructure:"force_path_style"` - DiscoverBucketRegion bool `mapstructure:"discover_bucket_region"` - SkipVerifyCertificateTestOnly bool `mapstructure:"skip_verify_certificate_test_only"` - ServerSideEncryption string `mapstructure:"server_side_encryption"` - ServerSideEncryptionKmsKeyID string `mapstructure:"server_side_encryption_kms_key_id"` - PreSignedExpiry time.Duration `mapstructure:"pre_signed_expiry"` - DisablePreSigned bool `mapstructure:"disable_pre_signed"` - DisablePreSignedUI bool `mapstructure:"disable_pre_signed_ui"` - ClientLogRetries bool `mapstructure:"client_log_retries"` - ClientLogRequest bool `mapstructure:"client_log_request"` + Region string `mapstructure:"region" json:"region"` + Endpoint string `mapstructure:"endpoint" json:"endpoint"` + MaxRetries int `mapstructure:"max_retries" json:"max_retries"` + ForcePathStyle bool `mapstructure:"force_path_style" json:"force_path_style"` + DiscoverBucketRegion bool `mapstructure:"discover_bucket_region" json:"discover_bucket_region"` + SkipVerifyCertificateTestOnly bool `mapstructure:"skip_verify_certificate_test_only" json:"skip_verify_certificate_test_only"` + ServerSideEncryption string `mapstructure:"server_side_encryption" json:"server_side_encryption"` + ServerSideEncryptionKmsKeyID string `mapstructure:"server_side_encryption_kms_key_id" json:"server_side_encryption_kms_key_id"` + PreSignedExpiry time.Duration `mapstructure:"pre_signed_expiry" json:"pre_signed_expiry"` + DisablePreSigned bool `mapstructure:"disable_pre_signed" json:"disable_pre_signed"` + DisablePreSignedUI bool `mapstructure:"disable_pre_signed_ui" json:"disable_pre_signed_ui"` + ClientLogRetries bool `mapstructure:"client_log_retries" json:"client_log_retries"` + ClientLogRequest bool `mapstructure:"client_log_request" json:"client_log_request"` WebIdentity *struct { - SessionDuration time.Duration `mapstructure:"session_duration"` - SessionExpiryWindow time.Duration `mapstructure:"session_expiry_window"` + SessionDuration time.Duration `mapstructure:"session_duration" json:"session_duration"` + SessionExpiryWindow time.Duration `mapstructure:"session_expiry_window" json:"session_expiry_window"` } `mapstructure:"web_identity"` - } `mapstructure:"s3"` + } `mapstructure:"s3" json:"s3"` Azure *struct { - TryTimeout time.Duration `mapstructure:"try_timeout"` - StorageAccount string `mapstructure:"storage_account"` - StorageAccessKey string `mapstructure:"storage_access_key"` - PreSignedExpiry time.Duration `mapstructure:"pre_signed_expiry"` - DisablePreSigned bool `mapstructure:"disable_pre_signed"` - DisablePreSignedUI bool `mapstructure:"disable_pre_signed_ui"` + TryTimeout time.Duration `mapstructure:"try_timeout" json:"try_timeout"` + StorageAccount string `mapstructure:"storage_account" json:"storage_account"` + StorageAccessKey string `mapstructure:"storage_access_key" json:"storage_access_key"` + PreSignedExpiry time.Duration `mapstructure:"pre_signed_expiry" json:"pre_signed_expiry"` + DisablePreSigned bool `mapstructure:"disable_pre_signed" json:"disable_pre_signed"` + DisablePreSignedUI bool `mapstructure:"disable_pre_signed_ui" json:"disable_pre_signed_ui"` // TestEndpointURL for testing purposes - TestEndpointURL string `mapstructure:"test_endpoint_url"` - } `mapstructure:"azure"` + TestEndpointURL string `mapstructure:"test_endpoint_url" json:"test_endpoint_url"` + } `mapstructure:"azure" json:"azure"` GS *struct { - S3Endpoint string `mapstructure:"s3_endpoint"` - CredentialsFile string `mapstructure:"credentials_file"` - CredentialsJSON string `mapstructure:"credentials_json"` - PreSignedExpiry time.Duration `mapstructure:"pre_signed_expiry"` - DisablePreSigned bool `mapstructure:"disable_pre_signed"` - DisablePreSignedUI bool `mapstructure:"disable_pre_signed_ui"` - } `mapstructure:"gs"` + S3Endpoint string `mapstructure:"s3_endpoint" json:"s3_endpoint"` + CredentialsFile string `mapstructure:"credentials_file" json:"credentials_file"` + CredentialsJSON string `mapstructure:"credentials_json" json:"credentials_json"` + PreSignedExpiry time.Duration `mapstructure:"pre_signed_expiry" json:"pre_signed_expiry"` + DisablePreSigned bool `mapstructure:"disable_pre_signed" json:"disable_pre_signed"` + DisablePreSignedUI bool `mapstructure:"disable_pre_signed_ui" json:"disable_pre_signed_ui"` + } `mapstructure:"gs" json:"gs"` } func (c *BlockStoreConfig) BlockstoreType() string { @@ -151,11 +151,11 @@ func (s SecureString) SecureValue() string { // S3AuthInfo holds S3-style authentication. type S3AuthInfo struct { - CredentialsFile string `mapstructure:"credentials_file"` + CredentialsFile string `mapstructure:"credentials_file" json:"credentials_file"` Profile string Credentials *struct { - AccessKeyID SecureString `mapstructure:"access_key_id"` - SecretAccessKey SecureString `mapstructure:"secret_access_key"` - SessionToken SecureString `mapstructure:"session_token"` + AccessKeyID SecureString `mapstructure:"access_key_id" json:"access_key_id"` + SecretAccessKey SecureString `mapstructure:"secret_access_key" json:"secret_access_key"` + SessionToken SecureString `mapstructure:"session_token" json:"session_token"` } } diff --git a/config/default.go b/config/default.go index 0e907cb1..0567b790 100644 --- a/config/default.go +++ b/config/default.go @@ -17,10 +17,10 @@ var defaultCfg = Config{ Blockstore: BlockStoreConfig{ Type: "local", Local: (*struct { - Path string `mapstructure:"path"` - ImportEnabled bool `mapstructure:"import_enabled"` - ImportHidden bool `mapstructure:"import_hidden"` - AllowedExternalPrefixes []string `mapstructure:"allowed_external_prefixes"` + Path string `mapstructure:"path" json:"path"` + ImportEnabled bool `mapstructure:"import_enabled" json:"import_enabled"` + ImportHidden bool `mapstructure:"import_hidden" json:"import_hidden"` + AllowedExternalPrefixes []string `mapstructure:"allowed_external_prefixes" json:"allowed_external_prefixes"` })(&struct { Path string ImportEnabled bool diff --git a/controller/branch_ctl.go b/controller/branch_ctl.go index 71aaddf1..e4af743f 100644 --- a/controller/branch_ctl.go +++ b/controller/branch_ctl.go @@ -96,11 +96,11 @@ func (bct BranchController) ListBranches(ctx context.Context, w *api.JiaozifsRes r := api.Branch{ CommitHash: branch.CommitHash.Hex(), CreatedAt: branch.CreatedAt, - CreatorID: branch.CreatorID, + CreatorId: branch.CreatorID, Description: branch.Description, - ID: branch.ID, + Id: branch.ID, Name: branch.Name, - RepositoryID: branch.RepositoryID, + RepositoryId: branch.RepositoryID, UpdatedAt: branch.UpdatedAt, } results = append(results, r) @@ -171,7 +171,7 @@ func (bct BranchController) CreateBranch(ctx context.Context, w *api.JiaozifsRes } // Create branch - newBranch := &models.Branches{ + newBranch := &models.Branch{ RepositoryID: repository.ID, CommitHash: commitHash, Name: body.Name, @@ -187,11 +187,11 @@ func (bct BranchController) CreateBranch(ctx context.Context, w *api.JiaozifsRes w.JSON(api.Branch{ CommitHash: newBranch.CommitHash.Hex(), CreatedAt: newBranch.CreatedAt, - CreatorID: newBranch.CreatorID, + CreatorId: newBranch.CreatorID, Description: newBranch.Description, - ID: newBranch.ID, + Id: newBranch.ID, Name: newBranch.Name, - RepositoryID: newBranch.RepositoryID, + RepositoryId: newBranch.RepositoryID, UpdatedAt: newBranch.UpdatedAt, }, http.StatusCreated) } @@ -268,11 +268,11 @@ func (bct BranchController) GetBranch(ctx context.Context, w *api.JiaozifsRespon w.JSON(api.Branch{ CommitHash: ref.CommitHash.Hex(), CreatedAt: ref.CreatedAt, - CreatorID: ref.CreatorID, + CreatorId: ref.CreatorID, Description: ref.Description, - ID: ref.ID, + Id: ref.ID, Name: ref.Name, - RepositoryID: ref.RepositoryID, + RepositoryId: ref.RepositoryID, UpdatedAt: ref.UpdatedAt, }) } diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index 8c737745..0ef00ae7 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -87,10 +87,10 @@ func (repositoryCtl RepositoryController) ListRepositoryOfAuthenticatedUser(ctx for _, repo := range repositories { r := api.Repository{ CreatedAt: repo.CreatedAt, - CreatorID: repo.CreatorID, + CreatorId: repo.CreatorID, Description: repo.Description, Head: repo.HEAD, - ID: repo.ID, + Id: repo.ID, Name: repo.Name, UpdatedAt: repo.UpdatedAt, } @@ -149,10 +149,10 @@ func (repositoryCtl RepositoryController) ListRepository(ctx context.Context, w for _, repo := range repositories { r := api.Repository{ CreatedAt: repo.CreatedAt, - CreatorID: repo.CreatorID, + CreatorId: repo.CreatorID, Description: repo.Description, Head: repo.HEAD, - ID: repo.ID, + Id: repo.ID, Name: repo.Name, UpdatedAt: repo.UpdatedAt, } @@ -185,9 +185,9 @@ func (repositoryCtl RepositoryController) CreateRepository(ctx context.Context, } var usePublicStorage = true - storageConfig := utils.StringValue(body.BlockStoreConfig) + storageConfig := utils.StringValue(body.BlockstoreConfig) repoID := uuid.New() - var storageNamespace string + var storageNamespace *string if len(storageConfig) > 0 { usePublicStorage = false var cfg = config.BlockStoreConfig{} @@ -202,12 +202,12 @@ func (repositoryCtl RepositoryController) CreateRepository(ctx context.Context, w.Forbidden() return } - storageNamespace = utils.StringValue(cfg.DefaultNamespacePrefix) + storageNamespace = cfg.DefaultNamespacePrefix } else { - storageNamespace = fmt.Sprintf("%s://%s", repositoryCtl.PublicStorageConfig.BlockstoreType(), repoID.String()) + storageNamespace = utils.String(fmt.Sprintf("%s://%s", repositoryCtl.PublicStorageConfig.BlockstoreType(), repoID.String())) } - defaultRef := &models.Branches{ + defaultRef := &models.Branch{ RepositoryID: repoID, CommitHash: hash.Hash{}, Name: DefaultBranchName, @@ -219,7 +219,7 @@ func (repositoryCtl RepositoryController) CreateRepository(ctx context.Context, ID: repoID, Name: body.Name, UsePublicStorage: usePublicStorage, - StorageAdapterParams: storageConfig, + StorageAdapterParams: &storageConfig, StorageNamespace: storageNamespace, Description: body.Description, HEAD: DefaultBranchName, @@ -246,10 +246,10 @@ func (repositoryCtl RepositoryController) CreateRepository(ctx context.Context, w.JSON(api.Repository{ CreatedAt: createdRepo.CreatedAt, - CreatorID: createdRepo.CreatorID, + CreatorId: createdRepo.CreatorID, Description: createdRepo.Description, Head: createdRepo.HEAD, - ID: createdRepo.ID, + Id: createdRepo.ID, Name: createdRepo.Name, UpdatedAt: createdRepo.UpdatedAt, }) @@ -403,7 +403,7 @@ func (repositoryCtl RepositoryController) GetCommitsInRepository(ctx context.Con if err == nil { modelCommit := commit.Commit() commits = append(commits, api.Commit{ - RepositoryID: modelCommit.RepositoryID, + RepositoryId: modelCommit.RepositoryID, Author: api.Signature{ Email: openapi_types.Email(modelCommit.Author.Email), Name: modelCommit.Author.Name, diff --git a/controller/user_ctl.go b/controller/user_ctl.go index 36df3a69..a993152d 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -80,7 +80,7 @@ func (userCtl UserController) Login(ctx context.Context, w *api.JiaozifsResponse func (userCtl UserController) Register(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.RegisterJSONRequestBody) { register := auth.Register{ - Username: body.Username, + Username: body.Name, Email: string(body.Email), Password: body.Password, } @@ -104,14 +104,14 @@ func (userCtl UserController) GetUserInfo(ctx context.Context, w *api.JiaozifsRe // perform GetUserInfo userInfo := api.UserInfo{ - CreatedAt: &user.CreatedAt, - CurrentSignInAt: &user.CurrentSignInAt, - CurrentSignInIP: &user.CurrentSignInIP, + Name: user.Name, Email: openapitypes.Email(user.Email), + CurrentSignInAt: &user.CurrentSignInAt, + CurrentSignInIp: &user.CurrentSignInIP, LastSignInAt: &user.LastSignInAt, - LastSignInIP: &user.LastSignInIP, - UpdateAt: &user.UpdatedAt, - Username: user.Name, + LastSignInIp: &user.LastSignInIP, + UpdatedAt: user.UpdatedAt, + CreatedAt: user.CreatedAt, } w.JSON(userInfo) } diff --git a/integrationtest/commit_test.go b/integrationtest/commit_test.go index cf7c3698..10fbbfc6 100644 --- a/integrationtest/commit_test.go +++ b/integrationtest/commit_test.go @@ -102,8 +102,8 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { result, err := api.ParseGetEntriesInRefResponse(resp) convey.So(err, convey.ShouldBeNil) convey.So(*result.JSON200, convey.ShouldHaveLength, 2) - convey.So(*(*result.JSON200)[0].Name, convey.ShouldEqual, "m.dat") - convey.So(*(*result.JSON200)[1].Name, convey.ShouldEqual, "x.dat") + convey.So((*result.JSON200)[0].Name, convey.ShouldEqual, "m.dat") + convey.So((*result.JSON200)[1].Name, convey.ShouldEqual, "x.dat") }) c.Convey("success to get entries in root", func() { @@ -118,8 +118,8 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { result, err := api.ParseGetEntriesInRefResponse(resp) convey.So(err, convey.ShouldBeNil) convey.So(*result.JSON200, convey.ShouldHaveLength, 2) - convey.So(*(*result.JSON200)[0].Name, convey.ShouldEqual, "g") - convey.So(*(*result.JSON200)[1].Name, convey.ShouldEqual, "m.dat") + convey.So((*result.JSON200)[0].Name, convey.ShouldEqual, "g") + convey.So((*result.JSON200)[1].Name, convey.ShouldEqual, "m.dat") }) }) @@ -201,8 +201,8 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { result, err := api.ParseGetEntriesInRefResponse(resp) convey.So(err, convey.ShouldBeNil) convey.So(*result.JSON200, convey.ShouldHaveLength, 2) - convey.So(*(*result.JSON200)[0].Name, convey.ShouldEqual, "m.dat") - convey.So(*(*result.JSON200)[1].Name, convey.ShouldEqual, "x.dat") + convey.So((*result.JSON200)[0].Name, convey.ShouldEqual, "m.dat") + convey.So((*result.JSON200)[1].Name, convey.ShouldEqual, "x.dat") }) c.Convey("success to get entries in root", func() { @@ -217,8 +217,8 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { result, err := api.ParseGetEntriesInRefResponse(resp) convey.So(err, convey.ShouldBeNil) convey.So(*result.JSON200, convey.ShouldHaveLength, 2) - convey.So(*(*result.JSON200)[0].Name, convey.ShouldEqual, "g") - convey.So(*(*result.JSON200)[1].Name, convey.ShouldEqual, "m.dat") + convey.So((*result.JSON200)[0].Name, convey.ShouldEqual, "g") + convey.So((*result.JSON200)[1].Name, convey.ShouldEqual, "m.dat") }) }) diff --git a/integrationtest/helper_test.go b/integrationtest/helper_test.go index 0a8771d4..6d9ae280 100644 --- a/integrationtest/helper_test.go +++ b/integrationtest/helper_test.go @@ -105,7 +105,7 @@ func SetupDaemon(t *testing.T, ctx context.Context) (string, Closer) { //nolint func createUser(ctx context.Context, c convey.C, client *api.Client, userName string) { c.Convey("register "+userName, func() { resp, err := client.Register(ctx, api.RegisterJSONRequestBody{ - Username: userName, + Name: userName, Password: "12345678", Email: "mock@gmail.com", }) diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index 7d7de182..f768323f 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -35,7 +35,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.CreateRepository(ctx, api.CreateRepository{ Description: utils.String("test resp"), Name: "happygo", - BlockStoreConfig: utils.String(cfg), + BlockstoreConfig: utils.String(cfg), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) @@ -46,7 +46,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { resp, err := client.CreateRepository(ctx, api.CreateRepository{ Description: utils.String("test resp"), Name: "happygo", - BlockStoreConfig: utils.String(cfg), + BlockstoreConfig: utils.String(cfg), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) @@ -63,7 +63,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { grp, err := api.ParseGetRepositoryResponse(resp) convey.So(err, convey.ShouldBeNil) convey.So(grp.JSON200.Head, convey.ShouldEqual, controller.DefaultBranchName) - fmt.Println(grp.JSON200.ID) + fmt.Println(grp.JSON200.Id) //check default branch created branchResp, err := client.GetBranch(ctx, userName, grp.JSON200.Name, &api.GetBranchParams{RefName: controller.DefaultBranchName}) convey.So(err, convey.ShouldBeNil) diff --git a/models/branch.go b/models/branch.go index 1975b179..971c410f 100644 --- a/models/branch.go +++ b/models/branch.go @@ -9,21 +9,21 @@ import ( "github.com/uptrace/bun" ) -type Branches struct { +type Branch struct { bun.BaseModel `bun:"table:branches"` - ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()" json:"id"` // RepositoryId which repository this branch belong - RepositoryID uuid.UUID `bun:"repository_id,type:uuid,notnull"` - CommitHash hash.Hash `bun:"commit_hash,type:bytea,notnull"` + RepositoryID uuid.UUID `bun:"repository_id,type:uuid,notnull" json:"repository_id"` + CommitHash hash.Hash `bun:"commit_hash,type:bytea,notnull" json:"commit_hash"` // Path name/path of branch - Name string `bun:"name,notnull"` + Name string `bun:"name,notnull" json:"name"` // Description - Description *string `bun:"description"` + Description *string `bun:"description" json:"description,omitempty"` // CreatorID who create this branch - CreatorID uuid.UUID `bun:"creator_id,type:uuid,notnull"` + CreatorID uuid.UUID `bun:"creator_id,type:uuid,notnull" json:"creator_id"` - CreatedAt time.Time `bun:"created_at"` - UpdatedAt time.Time `bun:"updated_at"` + CreatedAt time.Time `bun:"created_at,notnull" json:"created_at"` + UpdatedAt time.Time `bun:"updated_at,notnull" json:"updated_at"` } type GetBranchParams struct { @@ -124,11 +124,11 @@ func (gup *ListBranchParams) SetAmount(amount int) *ListBranchParams { } type IBranchRepo interface { - Insert(ctx context.Context, repo *Branches) (*Branches, error) + Insert(ctx context.Context, repo *Branch) (*Branch, error) UpdateByID(ctx context.Context, params *UpdateBranchParams) error - Get(ctx context.Context, id *GetBranchParams) (*Branches, error) + Get(ctx context.Context, id *GetBranchParams) (*Branch, error) - List(ctx context.Context, params *ListBranchParams) ([]*Branches, bool, error) + List(ctx context.Context, params *ListBranchParams) ([]*Branch, bool, error) Delete(ctx context.Context, params *DeleteBranchParams) (int64, error) } @@ -142,7 +142,7 @@ func NewBranchRepo(db bun.IDB) IBranchRepo { return &BranchRepo{db: db} } -func (r BranchRepo) Insert(ctx context.Context, branch *Branches) (*Branches, error) { +func (r BranchRepo) Insert(ctx context.Context, branch *Branch) (*Branch, error) { _, err := r.db.NewInsert().Model(branch).Exec(ctx) if err != nil { return nil, err @@ -150,8 +150,8 @@ func (r BranchRepo) Insert(ctx context.Context, branch *Branches) (*Branches, er return branch, nil } -func (r BranchRepo) Get(ctx context.Context, params *GetBranchParams) (*Branches, error) { - repo := &Branches{} +func (r BranchRepo) Get(ctx context.Context, params *GetBranchParams) (*Branch, error) { + repo := &Branch{} query := r.db.NewSelect().Model(repo) if uuid.Nil != params.ID { @@ -173,8 +173,8 @@ func (r BranchRepo) Get(ctx context.Context, params *GetBranchParams) (*Branches return repo, nil } -func (r BranchRepo) List(ctx context.Context, params *ListBranchParams) ([]*Branches, bool, error) { - branches := []*Branches{} +func (r BranchRepo) List(ctx context.Context, params *ListBranchParams) ([]*Branch, bool, error) { + var branches []*Branch query := r.db.NewSelect().Model(&branches) if uuid.Nil != params.RepositoryID { @@ -204,7 +204,7 @@ func (r BranchRepo) List(ctx context.Context, params *ListBranchParams) ([]*Bran } func (r BranchRepo) Delete(ctx context.Context, params *DeleteBranchParams) (int64, error) { - query := r.db.NewDelete().Model((*Branches)(nil)) + query := r.db.NewDelete().Model((*Branch)(nil)) if uuid.Nil != params.ID { query = query.Where("id = ?", params.ID) diff --git a/models/branch_test.go b/models/branch_test.go index 25983ca5..df706c3b 100644 --- a/models/branch_test.go +++ b/models/branch_test.go @@ -20,7 +20,7 @@ func TestRefRepoInsert(t *testing.T) { repo := models.NewBranchRepo(db) - branchModel := &models.Branches{} + branchModel := &models.Branch{} require.NoError(t, gofakeit.Struct(branchModel)) branchModel.Name = "feat/abc/aaa" newBranch, err := repo.Insert(ctx, branchModel) @@ -51,7 +51,7 @@ func TestRefRepoInsert(t *testing.T) { require.Len(t, list, 1) // SecondModel - secModel := &models.Branches{} + secModel := &models.Branch{} require.NoError(t, gofakeit.Struct(secModel)) secModel.RepositoryID = branch.RepositoryID secModel.Name = "feat/bba/ccc" diff --git a/models/commit.go b/models/commit.go index a6bd4f88..9183525d 100644 --- a/models/commit.go +++ b/models/commit.go @@ -12,35 +12,35 @@ import ( // Signature is used to identify who and when created a commit or tag. type Signature struct { // Name represents a person name. It is an arbitrary string. - Name string `bun:"name"` + Name string `bun:"name" json:"name"` // Email is an email, but it cannot be assumed to be well-formed. - Email string `bun:"email"` + Email string `bun:"email" json:"email"` // When is the timestamp of the signature. - When time.Time `bun:"when"` + When time.Time `bun:"when" json:"when"` } type Commit struct { bun.BaseModel `bun:"table:commits"` - Hash hash.Hash `bun:"hash,pk,type:bytea"` - RepositoryID uuid.UUID `bun:"repository_id,pk,type:uuid,notnull"` + Hash hash.Hash `bun:"hash,pk,type:bytea" json:"hash"` + RepositoryID uuid.UUID `bun:"repository_id,pk,type:uuid,notnull" json:"repository_id"` //////********commit********//////// // Author is the original author of the commit. - Author Signature `bun:"author,type:jsonb"` + Author Signature `bun:"author,notnull,type:jsonb" json:"author"` // Committer is the one performing the commit, might be different from // Author. - Committer Signature `bun:"committer,type:jsonb"` + Committer Signature `bun:"committer,notnull,type:jsonb" json:"committer"` // MergeTag is the embedded tag object when a merge commit is created by // merging a signed tag. - MergeTag string `bun:"merge_tag"` //todo + MergeTag string `bun:"merge_tag" json:"merge_tag"` // Message is the commit/tag message, contains arbitrary text. - Message string `bun:"message"` + Message string `bun:"message" json:"message"` // TreeHash is the hash of the root tree of the commit. - TreeHash hash.Hash `bun:"tree_hash,type:bytea,notnull"` + TreeHash hash.Hash `bun:"tree_hash,type:bytea,notnull" json:"tree_hash"` // ParentHashes are the hashes of the parent commits of the commit. - ParentHashes []hash.Hash `bun:"parent_hashes,type:bytea[]"` + ParentHashes []hash.Hash `bun:"parent_hashes,type:bytea[]" json:"parent_hashes"` - CreatedAt time.Time `bun:"created_at"` - UpdatedAt time.Time `bun:"updated_at"` + CreatedAt time.Time `bun:"created_at,notnull" json:"created_at"` + UpdatedAt time.Time `bun:"updated_at,notnull" json:"updated_at"` } func (commit *Commit) GetHash() (hash.Hash, error) { diff --git a/models/merge_request.go b/models/merge_request.go index b8388d9e..4f153324 100644 --- a/models/merge_request.go +++ b/models/merge_request.go @@ -12,23 +12,23 @@ type MergeStatus int type MergeRequest struct { bun.BaseModel `bun:"table:merge_requests"` - ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` - TargetBranch string `bun:"target_branch,notnull"` - SourceBranch string `bun:"source_branch,notnull"` - SourceRepoID uuid.UUID `bun:"source_repo_id,type:bytea,notnull"` - TargetRepoID uuid.UUID `bun:"target_repo_id,type:bytea,notnull"` - Title string `bun:"title,notnull"` - MergeStatus MergeStatus `bun:"merge_status,notnull"` - Description *string `bun:"description"` - - AuthorID uuid.UUID `bun:"author_id,type:bytea,notnull"` - - AssigneeID uuid.UUID `bun:"assignee_id,type:bytea"` - MergeUserID uuid.UUID `bun:"merge_user_id,type:bytea"` - ApprovalsBeforeMerge int `bun:"approvals_before_merge"` - - CreatedAt time.Time `bun:"created_at"` - UpdatedAt time.Time `bun:"updated_at"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()" json:"id"` + TargetBranch string `bun:"target_branch,notnull" json:"target_branch"` + SourceBranch string `bun:"source_branch,notnull" json:"source_branch"` + SourceRepoID uuid.UUID `bun:"source_repo_id,type:bytea,notnull" json:"source_repo_id"` + TargetRepoID uuid.UUID `bun:"target_repo_id,type:bytea,notnull" json:"target_repo_id"` + Title string `bun:"title,notnull" json:"title"` + MergeStatus MergeStatus `bun:"merge_status,notnull" json:"merge_status"` + Description *string `bun:"description" json:"description"` + + AuthorID uuid.UUID `bun:"author_id,type:bytea,notnull" json:"author_id"` + + AssigneeID uuid.UUID `bun:"assignee_id,type:bytea" json:"assignee_id"` + MergeUserID uuid.UUID `bun:"merge_user_id,type:bytea" json:"merge_user_id"` + ApprovalsBeforeMerge int `bun:"approvals_before_merge" json:"approvals_before_merge"` + + CreatedAt time.Time `bun:"created_at,notnull" json:"created_at"` + UpdatedAt time.Time `bun:"updated_at,notnull" json:"updated_at"` } type GetMergeRequestParams struct { diff --git a/models/migrations/20210505110026_init_project.go b/models/migrations/20210505110026_init_project.go index 55de291a..4b28923f 100644 --- a/models/migrations/20210505110026_init_project.go +++ b/models/migrations/20210505110026_init_project.go @@ -41,7 +41,7 @@ func init() { //ref _, err = db.NewCreateTable(). - Model((*models.Branches)(nil)). + Model((*models.Branch)(nil)). Exec(ctx) if err != nil { return err diff --git a/models/repository.go b/models/repository.go index 40cce5a7..cbcef063 100644 --- a/models/repository.go +++ b/models/repository.go @@ -8,27 +8,23 @@ import ( "github.com/uptrace/bun" ) -type StorageNamespace struct { - IsPublic bool `json:"is_public"` - Type string `json:"type"` -} - type Repository struct { - bun.BaseModel `bun:"table:repositories"` - ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` - Name string `bun:"name,unique:name_owner_unique,notnull"` - OwnerID uuid.UUID `bun:"owner_id,unique:name_owner_unique,type:uuid,notnull"` - HEAD string `bun:"head,notnull"` + bun.BaseModel `bun:"table:repositories" json:"bun_._base_model"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()" json:"id"` + Name string `bun:"name,unique:name_owner_unique,notnull" json:"name"` + OwnerID uuid.UUID `bun:"owner_id,unique:name_owner_unique,type:uuid,notnull" json:"owner_id"` + HEAD string `bun:"head,notnull" json:"head"` + + UsePublicStorage bool `bun:"use_public_storage,notnull" json:"use_public_storage"` + StorageNamespace *string `bun:"storage_namespace" json:"storage_namespace,omitempty"` - UsePublicStorage bool `bun:"use_public_storage,notnull"` - StorageAdapterParams string `bun:"storage_adapter,notnull"` - StorageNamespace string `bun:"storage_namespace"` + StorageAdapterParams *string `bun:"storage_adapter_params" json:"storage_adapter_params,omitempty"` - Description *string `bun:"description"` - CreatorID uuid.UUID `bun:"creator_id,type:uuid,notnull"` + Description *string `bun:"description" json:"description,omitempty"` + CreatorID uuid.UUID `bun:"creator_id,type:uuid,notnull" json:"creator_id"` - CreatedAt time.Time `bun:"created_at"` - UpdatedAt time.Time `bun:"updated_at"` + CreatedAt time.Time `bun:"created_at,notnull" json:"created_at"` + UpdatedAt time.Time `bun:"updated_at,notnull" json:"updated_at"` } type GetRepoParams struct { diff --git a/models/tag.go b/models/tag.go index b5a3840a..f7e8209d 100644 --- a/models/tag.go +++ b/models/tag.go @@ -11,23 +11,23 @@ import ( type Tag struct { bun.BaseModel `bun:"table:tags"` - Hash hash.Hash `bun:"hash,pk,type:bytea"` - RepositoryID uuid.UUID `bun:"repository_id,pk,type:uuid,notnull"` - Type ObjectType `bun:"type"` + Hash hash.Hash `bun:"hash,pk,type:bytea" json:"hash"` + RepositoryID uuid.UUID `bun:"repository_id,pk,type:uuid,notnull" json:"repository_id"` + Type ObjectType `bun:"type" json:"type"` //////********commit********//////// // Name of the tag. - Name string `bun:"name"` + Name string `bun:"name" json:"name"` // Tagger is the one who created the tag. - Tagger Signature `bun:"tagger,type:jsonb"` + Tagger Signature `bun:"tagger,type:jsonb" json:"tagger"` // TargetType is the object type of the target. - TargetType ObjectType `bun:"target_type"` + TargetType ObjectType `bun:"target_type" json:"target_type"` // Target is the hash of the target object. - Target hash.Hash `bun:"target,type:bytea"` + Target hash.Hash `bun:"target,type:bytea" json:"target"` // Message is the tag message, contains arbitrary text. - Message string `bun:"message"` + Message string `bun:"message" json:"message"` - CreatedAt time.Time `bun:"created_at"` - UpdatedAt time.Time `bun:"updated_at"` + CreatedAt time.Time `bun:"created_at" json:"created_at"` + UpdatedAt time.Time `bun:"updated_at" json:"updated_at"` } type ITagRepo interface { diff --git a/models/tree.go b/models/tree.go index 8c27e9ae..75c21eab 100644 --- a/models/tree.go +++ b/models/tree.go @@ -29,9 +29,9 @@ const ( ) type TreeEntry struct { - Name string `bun:"name"` - IsDir bool `bun:"is_dir"` - Hash hash.Hash `bun:"hash"` + Name string `bun:"name" json:"name"` + IsDir bool `bun:"is_dir" json:"is_dir"` + Hash hash.Hash `bun:"hash" json:"hash"` } func SortSubObjects(subObjects []TreeEntry) []TreeEntry { @@ -78,12 +78,12 @@ type Blob struct { Hash hash.Hash `bun:"hash,pk,type:bytea"` RepositoryID uuid.UUID `bun:"repository_id,pk,type:uuid,notnull"` CheckSum hash.Hash `bun:"check_sum,type:bytea"` - Type ObjectType `bun:"type"` + Type ObjectType `bun:"type,notnull"` Size int64 `bun:"size"` - Properties Property `bun:"properties,type:jsonb"` + Properties Property `bun:"properties,type:jsonb,notnull"` - CreatedAt time.Time `bun:"created_at"` - UpdatedAt time.Time `bun:"updated_at"` + CreatedAt time.Time `bun:"created_at,notnull"` + UpdatedAt time.Time `bun:"updated_at,notnull"` } func NewBlob(props Property, repoID uuid.UUID, checkSum hash.Hash, size int64) (*Blob, error) { @@ -146,15 +146,15 @@ func (blob *Blob) FileTree() *FileTree { type TreeNode struct { bun.BaseModel `bun:"table:trees"` - Hash hash.Hash `bun:"hash,pk,type:bytea"` - RepositoryID uuid.UUID `bun:"repository_id,pk,type:uuid,notnull"` + Hash hash.Hash `bun:"hash,pk,type:bytea" json:"hash"` + RepositoryID uuid.UUID `bun:"repository_id,pk,type:uuid,notnull" json:"repository_id"` - Type ObjectType `bun:"type"` - SubObjects []TreeEntry `bun:"subObjs,type:jsonb"` - Properties Property `bun:"properties,type:jsonb"` + Type ObjectType `bun:"type,notnull" json:"type"` + SubObjects []TreeEntry `bun:"sub_objects,type:jsonb" json:"sub_objects"` + Properties Property `bun:"properties,type:jsonb,notnull" json:"properties"` - CreatedAt time.Time `bun:"created_at"` - UpdatedAt time.Time `bun:"updated_at"` + CreatedAt time.Time `bun:"created_at,notnull" json:"created_at"` + UpdatedAt time.Time `bun:"updated_at,notnull" json:"updated_at"` } func NewTreeNode(props Property, repoID uuid.UUID, subObjects ...TreeEntry) (*TreeNode, error) { @@ -221,15 +221,15 @@ type FileTree struct { bun.BaseModel `bun:"table:trees"` Hash hash.Hash `bun:"hash,pk,type:bytea"` RepositoryID uuid.UUID `bun:"repository_id,pk,type:uuid,notnull"` - Type ObjectType `bun:"type"` - Size int64 `bun:"size"` CheckSum hash.Hash `bun:"check_sum,type:bytea"` - Properties Property `bun:"properties,type:jsonb"` + Type ObjectType `bun:"type,notnull"` + Size int64 `bun:"size"` + Properties Property `bun:"properties,type:jsonb,notnull"` //tree - SubObjects []TreeEntry `bun:"subObjs,type:jsonb"` + SubObjects []TreeEntry `bun:"sub_objects,type:jsonb" json:"sub_objects"` - CreatedAt time.Time `bun:"created_at"` - UpdatedAt time.Time `bun:"updated_at"` + CreatedAt time.Time `bun:"created_at,notnull" json:"created_at"` + UpdatedAt time.Time `bun:"updated_at,notnull" json:"updated_at"` } func (fileTree *FileTree) Blob() *Blob { diff --git a/models/user.go b/models/user.go index d82f267e..62ee9301 100644 --- a/models/user.go +++ b/models/user.go @@ -10,16 +10,16 @@ import ( type User struct { bun.BaseModel `bun:"table:users"` - ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` - Name string `bun:"name,notnull"` - Email string `bun:"email,notnull"` - EncryptedPassword string `bun:"encrypted_password"` - CurrentSignInAt time.Time `bun:"current_sign_in_at"` - LastSignInAt time.Time `bun:"last_sign_in_at"` - CurrentSignInIP string `bun:"current_sign_in_ip"` - LastSignInIP string `bun:"last_sign_in_ip"` - CreatedAt time.Time `bun:"created_at"` - UpdatedAt time.Time `bun:"updated_at"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()" json:"id"` + Name string `bun:"name,notnull" json:"name"` + Email string `bun:"email,notnull" json:"email"` + EncryptedPassword string `bun:"encrypted_password,notnull" json:"encrypted_password"` + CurrentSignInAt time.Time `bun:"current_sign_in_at" json:"current_sign_in_at"` + LastSignInAt time.Time `bun:"last_sign_in_at" json:"last_sign_in_at"` + CurrentSignInIP string `bun:"current_sign_in_ip" json:"current_sign_in_ip"` + LastSignInIP string `bun:"last_sign_in_ip" json:"last_sign_in_ip"` + CreatedAt time.Time `bun:"created_at,notnull" json:"created_at"` + UpdatedAt time.Time `bun:"updated_at,notnull" json:"updated_at"` } type GetUserParams struct { diff --git a/models/wip.go b/models/wip.go index 557fbe95..9058705b 100644 --- a/models/wip.go +++ b/models/wip.go @@ -18,15 +18,15 @@ const ( type WorkingInProcess struct { bun.BaseModel `bun:"table:wips"` - ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` - CurrentTree hash.Hash `bun:"current_tree,type:bytea,notnull"` - BaseCommit hash.Hash `bun:"base_commit,type:bytea,notnull"` - RepositoryID uuid.UUID `bun:"repository_id,unique:creator_id_repository_id_ref_id_unique,type:uuid,notnull"` - RefID uuid.UUID `bun:"ref_id,unique:creator_id_repository_id_ref_id_unique,type:uuid,notnull"` - State WipState `bun:"state,notnull"` - CreatorID uuid.UUID `bun:"creator_id,unique:creator_id_repository_id_ref_id_unique,type:uuid,notnull"` - CreatedAt time.Time `bun:"created_at"` - UpdatedAt time.Time `bun:"updated_at"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()" json:"id"` + CurrentTree hash.Hash `bun:"current_tree,type:bytea,notnull" json:"current_tree"` + BaseCommit hash.Hash `bun:"base_commit,type:bytea,notnull" json:"base_commit"` + RepositoryID uuid.UUID `bun:"repository_id,unique:creator_id_repository_id_ref_id_unique,type:uuid,notnull" json:"repository_id"` + RefID uuid.UUID `bun:"ref_id,unique:creator_id_repository_id_ref_id_unique,type:uuid,notnull" json:"ref_id"` + State WipState `bun:"state,notnull" json:"state"` + CreatorID uuid.UUID `bun:"creator_id,unique:creator_id_repository_id_ref_id_unique,type:uuid,notnull" json:"creator_id"` + CreatedAt time.Time `bun:"created_at" json:"created_at"` + UpdatedAt time.Time `bun:"updated_at" json:"updated_at"` } type GetWipParams struct { diff --git a/versionmgr/work_repo.go b/versionmgr/work_repo.go index 9be29b01..70094e9e 100644 --- a/versionmgr/work_repo.go +++ b/versionmgr/work_repo.go @@ -10,6 +10,8 @@ import ( "os" "time" + "github.com/jiaozifs/jiaozifs/utils" + logging "github.com/ipfs/go-log/v2" "github.com/jiaozifs/jiaozifs/block" "github.com/jiaozifs/jiaozifs/block/factory" @@ -39,7 +41,7 @@ type WorkRepository struct { //cache headTree *hash.Hash wip *models.WorkingInProcess - branch *models.Branches + branch *models.Branch commit *models.Commit } @@ -49,7 +51,7 @@ func NewWorkRepositoryFromConfig(ctx context.Context, operator *models.User, rep if repoModel.UsePublicStorage { adapter, err = factory.BuildBlockAdapter(ctx, publicAdapterConfig) } else { - adapter, err = AdapterFromConfig(ctx, repoModel.StorageAdapterParams) + adapter, err = AdapterFromConfig(ctx, *repoModel.StorageAdapterParams) } if err != nil { return nil, err @@ -92,7 +94,7 @@ func (repository *WorkRepository) WriteBlob(ctx context.Context, body io.Reader, address := pathutil.PathOfHash(checkSum) err = repository.adapter.Put(ctx, block.ObjectPointer{ - StorageNamespace: repository.repoModel.StorageNamespace, + StorageNamespace: utils.StringValue(repository.repoModel.StorageNamespace), IdentifierType: block.IdentifierTypeRelative, Identifier: address, }, contentLength, tempf, block.PutOpts{}) @@ -107,7 +109,7 @@ func (repository *WorkRepository) WriteBlob(ctx context.Context, body io.Reader, func (repository *WorkRepository) ReadBlob(ctx context.Context, blob *models.Blob, rangeSpec *string) (io.ReadCloser, error) { address := pathutil.PathOfHash(blob.CheckSum) pointer := block.ObjectPointer{ - StorageNamespace: repository.repoModel.StorageNamespace, + StorageNamespace: utils.StringValue(repository.repoModel.StorageNamespace), IdentifierType: block.IdentifierTypeRelative, Identifier: address, } @@ -325,7 +327,7 @@ func (repository *WorkRepository) Merge(ctx context.Context, merger *models.User return newCommit, nil } -func (repository *WorkRepository) setCurState(state WorkRepoState, wip *models.WorkingInProcess, branch *models.Branches, commit *models.Commit) { +func (repository *WorkRepository) setCurState(state WorkRepoState, wip *models.WorkingInProcess, branch *models.Branch, commit *models.Commit) { repository.state = state repository.wip = wip repository.branch = branch @@ -336,7 +338,7 @@ func (repository *WorkRepository) CurWip() *models.WorkingInProcess { return repository.wip } -func (repository *WorkRepository) CurBranch() *models.Branches { +func (repository *WorkRepository) CurBranch() *models.Branch { return repository.branch } diff --git a/versionmgr/work_repo_test.go b/versionmgr/work_repo_test.go index 0c3c3792..5194f94c 100644 --- a/versionmgr/work_repo_test.go +++ b/versionmgr/work_repo_test.go @@ -41,7 +41,7 @@ func TestTreeWriteBlob(t *testing.T) { repoModel := &models.Repository{} require.NoError(t, gofakeit.Struct(repoModel)) repoModel.CreatorID = userModel.ID - repoModel.StorageNamespace = "mem://data" + repoModel.StorageNamespace = utils.String("mem://data") repoModel, err = repo.RepositoryRepo().Insert(ctx, repoModel) require.NoError(t, err) @@ -79,10 +79,10 @@ func TestNewWorkRepositoryFromConfig(t *testing.T) { pubCfg := &config.BlockStoreConfig{ Type: "local", Local: (*struct { - Path string `mapstructure:"path"` - ImportEnabled bool `mapstructure:"import_enabled"` - ImportHidden bool `mapstructure:"import_hidden"` - AllowedExternalPrefixes []string `mapstructure:"allowed_external_prefixes"` + Path string `mapstructure:"path" json:"path"` + ImportEnabled bool `mapstructure:"import_enabled" json:"import_enabled"` + ImportHidden bool `mapstructure:"import_hidden" json:"import_hidden"` + AllowedExternalPrefixes []string `mapstructure:"allowed_external_prefixes" json:"allowed_external_prefixes"` })(&struct { Path string ImportEnabled bool @@ -99,7 +99,7 @@ func TestNewWorkRepositoryFromConfig(t *testing.T) { CreatedAt: time.Now(), UpdatedAt: time.Now(), CreatorID: user.ID, - StorageNamespace: "mem://data", + StorageNamespace: utils.String("mem://data"), }) require.NoError(t, err) newRepo, err := NewWorkRepositoryFromConfig(ctx, user, project, repo, pubCfg) @@ -111,10 +111,10 @@ func TestNewWorkRepositoryFromConfig(t *testing.T) { pubCfg := &config.BlockStoreConfig{ Type: "local", Local: (*struct { - Path string `mapstructure:"path"` - ImportEnabled bool `mapstructure:"import_enabled"` - ImportHidden bool `mapstructure:"import_hidden"` - AllowedExternalPrefixes []string `mapstructure:"allowed_external_prefixes"` + Path string `mapstructure:"path" json:"path"` + ImportEnabled bool `mapstructure:"import_enabled" json:"import_enabled"` + ImportHidden bool `mapstructure:"import_hidden" json:"import_hidden"` + AllowedExternalPrefixes []string `mapstructure:"allowed_external_prefixes" json:"allowed_external_prefixes"` })(&struct { Path string ImportEnabled bool @@ -131,7 +131,7 @@ func TestNewWorkRepositoryFromConfig(t *testing.T) { CreatedAt: time.Now(), UpdatedAt: time.Now(), CreatorID: user.ID, - StorageAdapterParams: storageCfg, + StorageAdapterParams: &storageCfg, }) require.NoError(t, err) newRepo, err := NewWorkRepositoryFromConfig(ctx, user, project, repo, pubCfg) @@ -143,10 +143,10 @@ func TestNewWorkRepositoryFromConfig(t *testing.T) { pubCfg := &config.BlockStoreConfig{ Type: "local", Local: (*struct { - Path string `mapstructure:"path"` - ImportEnabled bool `mapstructure:"import_enabled"` - ImportHidden bool `mapstructure:"import_hidden"` - AllowedExternalPrefixes []string `mapstructure:"allowed_external_prefixes"` + Path string `mapstructure:"path" json:"path"` + ImportEnabled bool `mapstructure:"import_enabled" json:"import_enabled"` + ImportHidden bool `mapstructure:"import_hidden" json:"import_hidden"` + AllowedExternalPrefixes []string `mapstructure:"allowed_external_prefixes" json:"allowed_external_prefixes"` })(&struct { Path string ImportEnabled bool @@ -163,7 +163,7 @@ func TestNewWorkRepositoryFromConfig(t *testing.T) { CreatedAt: time.Now(), UpdatedAt: time.Now(), CreatorID: user.ID, - StorageAdapterParams: storageCfg, + StorageAdapterParams: &storageCfg, }) require.NoError(t, err) _, err = NewWorkRepositoryFromConfig(ctx, user, project, repo, pubCfg) @@ -242,7 +242,7 @@ func makeRepository(ctx context.Context, repoRepo models.IRepositoryRepo, user * CreatedAt: time.Now(), UpdatedAt: time.Now(), CreatorID: user.ID, - StorageNamespace: "mem://data", + StorageNamespace: utils.String("mem://data"), }) } @@ -276,8 +276,8 @@ func makeCommit(ctx context.Context, commitRepo models.ICommitRepo, treeHash has return obj, nil } -func makeBranch(ctx context.Context, branchRepo models.IBranchRepo, user *models.User, name string, repoID uuid.UUID, commitHash hash.Hash) (*models.Branches, error) { - branch := &models.Branches{ +func makeBranch(ctx context.Context, branchRepo models.IBranchRepo, user *models.User, name string, repoID uuid.UUID, commitHash hash.Hash) (*models.Branch, error) { + branch := &models.Branch{ RepositoryID: repoID, CommitHash: commitHash, Name: name, From c3800571528086e7990a351bca3756c76102a1aa Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Mon, 25 Dec 2023 15:11:23 +0800 Subject: [PATCH 119/210] feat: ensure subobjects exist --- api/jiaozifs.gen.go | 72 +++++++++++++++++----------------- api/swagger.yml | 4 +- controller/commit_ctl.go | 2 +- controller/user_ctl.go | 6 +-- integrationtest/helper_test.go | 2 +- integrationtest/user_test.go | 2 +- models/tree.go | 10 +++-- models/tree_test.go | 24 ++++++++++++ versionmgr/worktree.go | 11 +++--- 9 files changed, 81 insertions(+), 52 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index f347301d..dbe1340e 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -296,8 +296,8 @@ type PaginationRepoAfter = time.Time // LoginJSONBody defines parameters for Login. type LoginJSONBody struct { + Name string `json:"name"` Password string `json:"password"` - Username string `json:"username"` } // DeleteObjectParams defines parameters for DeleteObject. @@ -6589,41 +6589,41 @@ var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 2d2a2856..d87a1397 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -1563,10 +1563,10 @@ paths: schema: type: object required: - - username + - name - password properties: - username: + name: type: string password: type: string diff --git a/controller/commit_ctl.go b/controller/commit_ctl.go index a812f69b..f325f160 100644 --- a/controller/commit_ctl.go +++ b/controller/commit_ctl.go @@ -44,7 +44,7 @@ func (commitCtl CommitController) GetEntriesInRef(ctx context.Context, w *api.Ji } refName := repository.HEAD - if params.Path != nil { + if params.Ref != nil { refName = *params.Ref } diff --git a/controller/user_ctl.go b/controller/user_ctl.go index a993152d..b7cc082c 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -35,7 +35,7 @@ type UserController struct { func (userCtl UserController) Login(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, body api.LoginJSONRequestBody) { // get user encryptedPassword by username - ep, err := userCtl.Repo.UserRepo().GetEPByName(ctx, body.Username) + ep, err := userCtl.Repo.UserRepo().GetEPByName(ctx, body.Name) if err != nil { w.Code(http.StatusUnauthorized) return @@ -56,13 +56,13 @@ func (userCtl UserController) Login(ctx context.Context, w *api.JiaozifsResponse return } - tokenString, err := auth.GenerateJWTLogin(secretKey, body.Username, loginTime, expires) + tokenString, err := auth.GenerateJWTLogin(secretKey, body.Name, loginTime, expires) if err != nil { w.Error(err) return } - userCtlLog.Infof("usert %s login successful", body.Username) + userCtlLog.Infof("user %s login successful", body.Name) internalAuthSession, _ := userCtl.SessionStore.Get(r, auth.InternalAuthSessionName) internalAuthSession.Values[auth.TokenSessionKeyName] = tokenString diff --git a/integrationtest/helper_test.go b/integrationtest/helper_test.go index 6d9ae280..361acb02 100644 --- a/integrationtest/helper_test.go +++ b/integrationtest/helper_test.go @@ -117,7 +117,7 @@ func createUser(ctx context.Context, c convey.C, client *api.Client, userName st func loginAndSwitch(ctx context.Context, c convey.C, client *api.Client, userName string) { c.Convey("login "+userName, func() { resp, err := client.Login(ctx, api.LoginJSONRequestBody{ - Username: userName, + Name: userName, Password: "12345678", }) convey.So(err, convey.ShouldBeNil) diff --git a/integrationtest/user_test.go b/integrationtest/user_test.go index 76cf40fa..2ee208a3 100644 --- a/integrationtest/user_test.go +++ b/integrationtest/user_test.go @@ -23,7 +23,7 @@ func UserSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("login fail", func() { resp, err := client.Login(ctx, api.LoginJSONRequestBody{ - Username: "admin", + Name: "admin", Password: " vvvvvvvv", }) convey.So(err, convey.ShouldBeNil) diff --git a/models/tree.go b/models/tree.go index 75c21eab..551bfecf 100644 --- a/models/tree.go +++ b/models/tree.go @@ -43,8 +43,9 @@ func SortSubObjects(subObjects []TreeEntry) []TreeEntry { func NewRootTreeEntry(hash hash.Hash) TreeEntry { return TreeEntry{ - Name: "", - Hash: hash, + Name: "", + Hash: hash, + IsDir: true, } } func (treeEntry TreeEntry) Equal(other TreeEntry) bool { @@ -158,6 +159,9 @@ type TreeNode struct { } func NewTreeNode(props Property, repoID uuid.UUID, subObjects ...TreeEntry) (*TreeNode, error) { + if subObjects == nil { + subObjects = make([]TreeEntry, 0) //to ensure tree entry not null + } newTree := &TreeNode{ Type: TreeObject, RepositoryID: repoID, @@ -226,7 +230,7 @@ type FileTree struct { Size int64 `bun:"size"` Properties Property `bun:"properties,type:jsonb,notnull"` //tree - SubObjects []TreeEntry `bun:"sub_objects,type:jsonb" json:"sub_objects"` + SubObjects []TreeEntry `bun:"sub_objects,type:jsonb,notnull" json:"sub_objects"` CreatedAt time.Time `bun:"created_at,notnull" json:"created_at"` UpdatedAt time.Time `bun:"updated_at,notnull" json:"updated_at"` diff --git a/models/tree_test.go b/models/tree_test.go index deb9a303..e626368c 100644 --- a/models/tree_test.go +++ b/models/tree_test.go @@ -4,6 +4,8 @@ import ( "context" "testing" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" "github.com/google/uuid" @@ -72,3 +74,25 @@ func TestObjectRepo_Insert(t *testing.T) { require.ErrorIs(t, err, models.ErrRepoIDMisMatch) }) } + +func TestNewTreeNode(t *testing.T) { + id, err := uuid.Parse("a91ef678-1980-4b26-9bb9-eadc9f366429") + require.NoError(t, err) + + t.Run("no subobjects", func(t *testing.T) { + node, err := models.NewTreeNode(models.Property{Mode: filemode.Dir}, id) + require.NoError(t, err) + require.NotNil(t, node.SubObjects) + require.Equal(t, "03c2737fb833f979f2bb5398248e8e64", node.Hash.Hex()) + }) + + t.Run("no subobjects", func(t *testing.T) { + node, err := models.NewTreeNode(models.Property{Mode: filemode.Dir}, id, models.TreeEntry{ + Name: "a.txt", + Hash: hash.Hash("aaa"), + }) + require.NoError(t, err) + require.NotNil(t, node.SubObjects) + require.Equal(t, "27d9fbf6d43195f34404a94c0de707a2", node.Hash.Hex()) + }) +} diff --git a/versionmgr/worktree.go b/versionmgr/worktree.go index 66c3b1e6..b93e98cb 100644 --- a/versionmgr/worktree.go +++ b/versionmgr/worktree.go @@ -17,8 +17,9 @@ import ( ) var EmptyRoot = &models.TreeNode{ - Hash: hash.Hash([]byte{}), - Type: models.TreeObject, + Hash: hash.Hash([]byte{}), + Type: models.TreeObject, + SubObjects: make([]models.TreeEntry, 0), } var EmptyDirEntry = models.TreeEntry{ @@ -69,11 +70,11 @@ func (workTree *WorkTree) RepositoryID() uuid.UUID { } func (workTree *WorkTree) AppendDirectEntry(ctx context.Context, treeEntry models.TreeEntry) (*models.TreeNode, error) { - chilren, err := workTree.root.Children() + children, err := workTree.root.Children() if err != nil { return nil, err } - for _, node := range chilren { + for _, node := range children { if node.Name() == treeEntry.Name { return nil, ErrEntryExit } @@ -94,7 +95,7 @@ func (workTree *WorkTree) AppendDirectEntry(ctx context.Context, treeEntry model } func (workTree *WorkTree) DeleteDirectEntry(ctx context.Context, name string) (*models.TreeNode, bool, error) { - var subObjects []models.TreeEntry + subObjects := []models.TreeEntry{} //ensure subobject not nul for _, sub := range workTree.root.SubObjects() { if sub.Name != name { //filter tree entry by name subObjects = append(subObjects, sub) From 13f6718b8d571a056a41c9b4b69d0c9e04d9834c Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Mon, 25 Dec 2023 19:39:50 +0800 Subject: [PATCH 120/210] feat; allow any file for upload object api --- api/api_impl/server.go | 91 ++++++++++++++++++++++++++++++++++--- api/custom_response.go | 3 +- api/custom_response_test.go | 6 ++- versionmgr/work_repo.go | 4 +- 4 files changed, 92 insertions(+), 12 deletions(-) diff --git a/api/api_impl/server.go b/api/api_impl/server.go index 22c8ef2c..e3426605 100644 --- a/api/api_impl/server.go +++ b/api/api_impl/server.go @@ -3,13 +3,19 @@ package apiimpl import ( "context" "errors" + "fmt" "net" "net/http" "net/url" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/routers/gorillamux" "github.com/MadAppGang/httplog" "github.com/flowchartsman/swaggerui" "github.com/getkin/kin-openapi/openapi3filter" + "github.com/getkin/kin-openapi/routers" "github.com/go-chi/chi/v5" "github.com/gorilla/sessions" logging "github.com/ipfs/go-log/v2" @@ -18,14 +24,16 @@ import ( "github.com/jiaozifs/jiaozifs/auth/crypt" "github.com/jiaozifs/jiaozifs/config" "github.com/jiaozifs/jiaozifs/models" - middleware "github.com/oapi-codegen/nethttp-middleware" "github.com/rs/cors" "go.uber.org/fx" ) var log = logging.Logger("rpc") -const APIV1Prefix = "/api/v1" +const ( + APIV1Prefix = "/api/v1" + extensionValidationExcludeBody = "x-validation-exclude-body" +) func SetupAPI(lc fx.Lifecycle, apiConfig *config.APIConfig, secretStore crypt.SecretStore, sessionStore sessions.Store, repo models.IRepo, controller APIController) error { swagger, err := api.GetSwagger() @@ -53,11 +61,8 @@ func SetupAPI(lc fx.Lifecycle, apiConfig *config.APIConfig, secretStore crypt.Se // Use our validation middleware to check all requests against the // OpenAPI schema. apiRouter := r.With( - middleware.OapiRequestValidatorWithOptions(swagger, &middleware.Options{ - Options: openapi3filter.Options{ - AuthenticationFunc: openapi3filter.NoopAuthenticationFunc, - }, - SilenceServersWarning: true, + OapiRequestValidatorWithOptions(swagger, &openapi3filter.Options{ + AuthenticationFunc: openapi3filter.NoopAuthenticationFunc, }), auth.Middleware(swagger, nil, secretStore, repo.UserRepo(), sessionStore), ) @@ -94,3 +99,75 @@ func SetupAPI(lc fx.Lifecycle, apiConfig *config.APIConfig, secretStore crypt.Se }) return nil } + +// OapiRequestValidatorWithOptions Creates middleware to validate request by swagger spec. +func OapiRequestValidatorWithOptions(swagger *openapi3.T, options *openapi3filter.Options) func(next http.Handler) http.Handler { + router, err := gorillamux.NewRouter(swagger) + if err != nil { + panic(err) + } + + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + // validate request + if statusCode, err := validateRequest(r, router, options); err != nil { + http.Error(w, err.Error(), statusCode) + return + } + + // serve + next.ServeHTTP(w, r) + }) + } +} + +// validateRequest is called from the middleware above and actually does the work +// of validating a request. +func validateRequest(r *http.Request, router routers.Router, options *openapi3filter.Options) (int, error) { + // Find route + route, pathParams, err := router.FindRoute(r) + if err != nil { + return http.StatusNotFound, err // We failed to find a matching route for the request. + } + + // Validate request + requestValidationInput := &openapi3filter.RequestValidationInput{ + Request: r, + PathParams: pathParams, + Route: route, + } + + if options != nil { + optCopy := *options + requestValidationInput.Options = &optCopy + } + + if _, ok := route.Operation.Extensions[extensionValidationExcludeBody]; ok { + requestValidationInput.Options.ExcludeRequestBody = true + } + + if err := openapi3filter.ValidateRequest(r.Context(), requestValidationInput); err != nil { + me := openapi3.MultiError{} + if errors.As(err, &me) { + return http.StatusBadRequest, err + } + + switch e := err.(type) { + case *openapi3filter.RequestError: + // We've got a bad request + // Split up the verbose error by lines and return the first one + // openapi errors seem to be multi-line with a decent message on the first + errorLines := strings.Split(e.Error(), "\n") + return http.StatusBadRequest, fmt.Errorf(errorLines[0]) + case *openapi3filter.SecurityRequirementsError: + return http.StatusUnauthorized, err + default: + // This should never happen today, but if our upstream code changes, + // we don't want to crash the server, so handle the unexpected error. + return http.StatusInternalServerError, fmt.Errorf("error validating route: %s", err.Error()) + } + } + + return http.StatusOK, nil +} diff --git a/api/custom_response.go b/api/custom_response.go index f31d0304..a273a743 100644 --- a/api/custom_response.go +++ b/api/custom_response.go @@ -58,7 +58,8 @@ func (response *JiaozifsResponse) BadRequest(msg string) { // Error response with 500 and error message func (response *JiaozifsResponse) Error(err error) { if errors.Is(err, models.ErrNotFound) { - response.NotFound() + response.WriteHeader(http.StatusNotFound) + _, _ = response.Write([]byte(err.Error())) return } if errors.Is(err, auth.ErrUserNotFound) { diff --git a/api/custom_response_test.go b/api/custom_response_test.go index c09ca1a6..a7461177 100644 --- a/api/custom_response_test.go +++ b/api/custom_response_test.go @@ -31,7 +31,7 @@ func TestJiaozifsResponse(t *testing.T) { jzResp.Forbidden() }) - t.Run("not found", func(t *testing.T) { + t.Run("Unauthorized", func(t *testing.T) { ctrl := gomock.NewController(t) resp := NewMockResponseWriter(ctrl) jzResp := JiaozifsResponse{resp} @@ -82,8 +82,10 @@ func TestJiaozifsResponse(t *testing.T) { resp := NewMockResponseWriter(ctrl) jzResp := JiaozifsResponse{resp} + err := fmt.Errorf("mock %w", models.ErrNotFound) resp.EXPECT().WriteHeader(http.StatusNotFound) - jzResp.Error(fmt.Errorf("mock %w", models.ErrNotFound)) + resp.EXPECT().Write([]byte(err.Error())) + jzResp.Error(err) }) t.Run("error no auth", func(t *testing.T) { diff --git a/versionmgr/work_repo.go b/versionmgr/work_repo.go index 70094e9e..34a6d25a 100644 --- a/versionmgr/work_repo.go +++ b/versionmgr/work_repo.go @@ -167,11 +167,11 @@ func (repository *WorkRepository) CheckOut(ctx context.Context, refType WorkRepo if refType == InWip { ref, err := repository.repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.repoModel.ID).SetName(refName)) if err != nil { - return err + return fmt.Errorf("unable to get branch %s of repository %s: %w", refName, repository.repoModel.Name, err) } wip, err := repository.repo.WipRepo().Get(ctx, models.NewGetWipParams().SetCreatorID(repository.operator.ID).SetRepositoryID(repository.repoModel.ID).SetRefID(ref.ID)) if err != nil { - return err + return fmt.Errorf("unable to get wip of repository %s branch %s: %w", repository.repoModel.Name, refName, err) } treeHash = wip.CurrentTree repository.setCurState(InWip, wip, ref, nil) From 87754027e90e6af4222662ba3fa7da782dd80825 Mon Sep 17 00:00:00 2001 From: brown Date: Mon, 25 Dec 2023 21:39:42 +0800 Subject: [PATCH 121/210] feat: add commit pagination --- api/jiaozifs.gen.go | 193 +++++++++++++++++++++++------------ api/swagger.yml | 10 ++ controller/repository_ctl.go | 7 ++ integrationtest/repo_test.go | 28 +++++ integrationtest/root_test.go | 10 +- 5 files changed, 175 insertions(+), 73 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index dbe1340e..a9908ece 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -288,6 +288,9 @@ type PaginationAmount = int // PaginationBranchAfter defines model for PaginationBranchAfter. type PaginationBranchAfter = string +// PaginationCommitAfter defines model for PaginationCommitAfter. +type PaginationCommitAfter = time.Time + // PaginationPrefix defines model for PaginationPrefix. type PaginationPrefix = string @@ -378,6 +381,12 @@ type ListBranchesParams struct { // GetCommitsInRepositoryParams defines parameters for GetCommitsInRepository. type GetCommitsInRepositoryParams struct { + // After return items after this value + After *PaginationCommitAfter `form:"after,omitempty" json:"after,omitempty"` + + // Amount how many items to return + Amount *PaginationAmount `form:"amount,omitempty" json:"amount,omitempty"` + // RefName ref(branch/tag) name RefName *string `form:"refName,omitempty" json:"refName,omitempty"` } @@ -1903,6 +1912,38 @@ func NewGetCommitsInRepositoryRequest(server string, owner string, repository st if params != nil { queryValues := queryURL.Query() + if params.After != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "after", runtime.ParamLocationQuery, *params.After); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Amount != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "amount", runtime.ParamLocationQuery, *params.Amount); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + if params.RefName != nil { if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, *params.RefName); err != nil { @@ -5614,6 +5655,22 @@ func (siw *ServerInterfaceWrapper) GetCommitsInRepository(w http.ResponseWriter, // Parameter object where we will unmarshal all parameters from the context var params GetCommitsInRepositoryParams + // ------------- Optional query parameter "after" ------------- + + err = runtime.BindQueryParameter("form", true, false, "after", r.URL.Query(), ¶ms.After) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "after", Err: err}) + return + } + + // ------------- Optional query parameter "amount" ------------- + + err = runtime.BindQueryParameter("form", true, false, "amount", r.URL.Query(), ¶ms.Amount) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "amount", Err: err}) + return + } + // ------------- Optional query parameter "refName" ------------- err = runtime.BindQueryParameter("form", true, false, "refName", r.URL.Query(), ¶ms.RefName) @@ -6556,74 +6613,74 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index d87a1397..89b5a63c 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -40,6 +40,14 @@ components: schema: type: string format: date-time + + PaginationCommitAfter: + in: query + name: after + description: return items after this value + schema: + type: string + format: date-time PaginationAmount: in: query @@ -1260,6 +1268,8 @@ paths: operationId: getCommitsInRepository summary: get commits in repository parameters: + - $ref: "#/components/parameters/PaginationCommitAfter" + - $ref: "#/components/parameters/PaginationAmount" - in: query name: refName description: ref(branch/tag) name diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index 0ef00ae7..52d4f84b 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -348,6 +348,7 @@ func (repositoryCtl RepositoryController) UpdateRepository(ctx context.Context, w.Error(err) return } + w.OK() } func (repositoryCtl RepositoryController) GetCommitsInRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.GetCommitsInRepositoryParams) { @@ -401,6 +402,12 @@ func (repositoryCtl RepositoryController) GetCommitsInRepository(ctx context.Con for { commit, err := iter.Next() if err == nil { + if params.After != nil && commit.Commit().CreatedAt.Add(time.Nanosecond).After(*params.After) { + continue + } + if params.Amount != nil && len(commits) == *params.Amount { + break + } modelCommit := commit.Commit() commits = append(commits, api.Commit{ RepositoryId: modelCommit.RepositoryID, diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index f768323f..4e0de2fd 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -394,6 +394,34 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(*result.JSON200, convey.ShouldHaveLength, 1) convey.So((*result.JSON200)[0].Message, convey.ShouldEqual, "first commit") }) + + uploadObject(ctx, c, client, "add sec object", userName, repoName, controller.DefaultBranchName, "b.txt") + commitWip(ctx, c, client, "commit sec object", userName, repoName, controller.DefaultBranchName, "second commit") + c.Convey("success get commits by params", func() { + resp, err := client.GetCommitsInRepository(ctx, userName, repoName, &api.GetCommitsInRepositoryParams{ + RefName: utils.String(controller.DefaultBranchName), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseGetCommitsInRepositoryResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(*result.JSON200, convey.ShouldHaveLength, 2) + convey.So((*result.JSON200)[0].Message, convey.ShouldEqual, "second commit") + + newResp, err := client.GetCommitsInRepository(ctx, userName, repoName, &api.GetCommitsInRepositoryParams{ + After: utils.Time((*result.JSON200)[0].CreatedAt), + Amount: utils.Int(1), + RefName: utils.String(controller.DefaultBranchName), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + newResult, err := api.ParseGetCommitsInRepositoryResponse(newResp) + convey.So(err, convey.ShouldBeNil) + convey.So(*newResult.JSON200, convey.ShouldHaveLength, 1) + convey.So((*newResult.JSON200)[0].Message, convey.ShouldEqual, "first commit") + }) }) c.Convey("delete repository", func(c convey.C) { diff --git a/integrationtest/root_test.go b/integrationtest/root_test.go index 41017f7a..c9b48a1e 100644 --- a/integrationtest/root_test.go +++ b/integrationtest/root_test.go @@ -14,9 +14,9 @@ func TestSpec(t *testing.T) { convey.Convey("user test", t, UserSpec(ctx, urlStr)) convey.Convey("repo test", t, RepoSpec(ctx, urlStr)) - convey.Convey("branch test", t, BranchSpec(ctx, urlStr)) - convey.Convey("wip test", t, WipSpec(ctx, urlStr)) - convey.Convey("object test", t, ObjectSpec(ctx, urlStr)) - convey.Convey("wip object test", t, WipObjectSpec(ctx, urlStr)) - convey.Convey("commit test", t, GetEntriesInRefSpec(ctx, urlStr)) + // convey.Convey("branch test", t, BranchSpec(ctx, urlStr)) + // convey.Convey("wip test", t, WipSpec(ctx, urlStr)) + // convey.Convey("object test", t, ObjectSpec(ctx, urlStr)) + // convey.Convey("wip object test", t, WipObjectSpec(ctx, urlStr)) + // convey.Convey("commit test", t, GetEntriesInRefSpec(ctx, urlStr)) } From e1165ce951d834a5e397a6a0df3000ac2ba13514 Mon Sep 17 00:00:00 2001 From: brown Date: Mon, 25 Dec 2023 21:56:26 +0800 Subject: [PATCH 122/210] test: add test where not covered --- integrationtest/repo_test.go | 8 +++++--- integrationtest/root_test.go | 10 +++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index 4e0de2fd..36ee2b7a 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -397,6 +397,8 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { uploadObject(ctx, c, client, "add sec object", userName, repoName, controller.DefaultBranchName, "b.txt") commitWip(ctx, c, client, "commit sec object", userName, repoName, controller.DefaultBranchName, "second commit") + uploadObject(ctx, c, client, "add third object", userName, repoName, controller.DefaultBranchName, "c.txt") + commitWip(ctx, c, client, "commit third object", userName, repoName, controller.DefaultBranchName, "third commit") c.Convey("success get commits by params", func() { resp, err := client.GetCommitsInRepository(ctx, userName, repoName, &api.GetCommitsInRepositoryParams{ RefName: utils.String(controller.DefaultBranchName), @@ -406,8 +408,8 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { result, err := api.ParseGetCommitsInRepositoryResponse(resp) convey.So(err, convey.ShouldBeNil) - convey.So(*result.JSON200, convey.ShouldHaveLength, 2) - convey.So((*result.JSON200)[0].Message, convey.ShouldEqual, "second commit") + convey.So(*result.JSON200, convey.ShouldHaveLength, 3) + convey.So((*result.JSON200)[0].Message, convey.ShouldEqual, "third commit") newResp, err := client.GetCommitsInRepository(ctx, userName, repoName, &api.GetCommitsInRepositoryParams{ After: utils.Time((*result.JSON200)[0].CreatedAt), @@ -420,7 +422,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { newResult, err := api.ParseGetCommitsInRepositoryResponse(newResp) convey.So(err, convey.ShouldBeNil) convey.So(*newResult.JSON200, convey.ShouldHaveLength, 1) - convey.So((*newResult.JSON200)[0].Message, convey.ShouldEqual, "first commit") + convey.So((*newResult.JSON200)[0].Message, convey.ShouldEqual, "second commit") }) }) diff --git a/integrationtest/root_test.go b/integrationtest/root_test.go index c9b48a1e..41017f7a 100644 --- a/integrationtest/root_test.go +++ b/integrationtest/root_test.go @@ -14,9 +14,9 @@ func TestSpec(t *testing.T) { convey.Convey("user test", t, UserSpec(ctx, urlStr)) convey.Convey("repo test", t, RepoSpec(ctx, urlStr)) - // convey.Convey("branch test", t, BranchSpec(ctx, urlStr)) - // convey.Convey("wip test", t, WipSpec(ctx, urlStr)) - // convey.Convey("object test", t, ObjectSpec(ctx, urlStr)) - // convey.Convey("wip object test", t, WipObjectSpec(ctx, urlStr)) - // convey.Convey("commit test", t, GetEntriesInRefSpec(ctx, urlStr)) + convey.Convey("branch test", t, BranchSpec(ctx, urlStr)) + convey.Convey("wip test", t, WipSpec(ctx, urlStr)) + convey.Convey("object test", t, ObjectSpec(ctx, urlStr)) + convey.Convey("wip object test", t, WipObjectSpec(ctx, urlStr)) + convey.Convey("commit test", t, GetEntriesInRefSpec(ctx, urlStr)) } From 6720760eb89ca23df050da3850aa52ad43dafdef Mon Sep 17 00:00:00 2001 From: brown Date: Tue, 26 Dec 2023 11:25:55 +0800 Subject: [PATCH 123/210] fix: replace time comparison object --- api/jiaozifs.gen.go | 131 ++++++++++++++++++----------------- api/swagger.yml | 2 +- controller/repository_ctl.go | 11 ++- integrationtest/repo_test.go | 3 +- 4 files changed, 78 insertions(+), 69 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index a9908ece..69b8eeb7 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -289,7 +289,7 @@ type PaginationAmount = int type PaginationBranchAfter = string // PaginationCommitAfter defines model for PaginationCommitAfter. -type PaginationCommitAfter = time.Time +type PaginationCommitAfter = string // PaginationPrefix defines model for PaginationPrefix. type PaginationPrefix = string @@ -6617,70 +6617,71 @@ var swaggerSpec = []string{ "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", + "AAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 89b5a63c..bde7fe0f 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -47,7 +47,7 @@ components: description: return items after this value schema: type: string - format: date-time + format: date-time-ns PaginationAmount: in: query diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index 52d4f84b..252e30d8 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -402,8 +402,15 @@ func (repositoryCtl RepositoryController) GetCommitsInRepository(ctx context.Con for { commit, err := iter.Next() if err == nil { - if params.After != nil && commit.Commit().CreatedAt.Add(time.Nanosecond).After(*params.After) { - continue + if params.After != nil { + parseTime, err := time.Parse(time.RFC3339Nano, *params.After) + if err != nil { + w.Error(err) + return + } + if commit.Commit().Committer.When.Add(time.Nanosecond).After(parseTime) { + continue + } } if params.Amount != nil && len(commits) == *params.Amount { break diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index 36ee2b7a..dbfdb5c1 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net/http" + "time" "github.com/jiaozifs/jiaozifs/api" apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" @@ -412,7 +413,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So((*result.JSON200)[0].Message, convey.ShouldEqual, "third commit") newResp, err := client.GetCommitsInRepository(ctx, userName, repoName, &api.GetCommitsInRepositoryParams{ - After: utils.Time((*result.JSON200)[0].CreatedAt), + After: utils.String((*result.JSON200)[0].Committer.When.Format(time.RFC3339Nano)), Amount: utils.Int(1), RefName: utils.String(controller.DefaultBranchName), }) From f9eed54e669a7f2e013c607a58c3d9b6ab63623c Mon Sep 17 00:00:00 2001 From: brown Date: Tue, 26 Dec 2023 11:45:50 +0800 Subject: [PATCH 124/210] test: add tests to test where not covered --- integrationtest/repo_test.go | 9 +++++++++ integrationtest/root_test.go | 10 +++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index dbfdb5c1..a9e104d9 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -425,6 +425,15 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(*newResult.JSON200, convey.ShouldHaveLength, 1) convey.So((*newResult.JSON200)[0].Message, convey.ShouldEqual, "second commit") }) + + c.Convey("failed get commits by wrong params", func() { + resp, err := client.GetCommitsInRepository(ctx, userName, repoName, &api.GetCommitsInRepositoryParams{ + After: utils.String("123"), + RefName: utils.String(controller.DefaultBranchName), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusInternalServerError) + }) }) c.Convey("delete repository", func(c convey.C) { diff --git a/integrationtest/root_test.go b/integrationtest/root_test.go index 41017f7a..c9b48a1e 100644 --- a/integrationtest/root_test.go +++ b/integrationtest/root_test.go @@ -14,9 +14,9 @@ func TestSpec(t *testing.T) { convey.Convey("user test", t, UserSpec(ctx, urlStr)) convey.Convey("repo test", t, RepoSpec(ctx, urlStr)) - convey.Convey("branch test", t, BranchSpec(ctx, urlStr)) - convey.Convey("wip test", t, WipSpec(ctx, urlStr)) - convey.Convey("object test", t, ObjectSpec(ctx, urlStr)) - convey.Convey("wip object test", t, WipObjectSpec(ctx, urlStr)) - convey.Convey("commit test", t, GetEntriesInRefSpec(ctx, urlStr)) + // convey.Convey("branch test", t, BranchSpec(ctx, urlStr)) + // convey.Convey("wip test", t, WipSpec(ctx, urlStr)) + // convey.Convey("object test", t, ObjectSpec(ctx, urlStr)) + // convey.Convey("wip object test", t, WipObjectSpec(ctx, urlStr)) + // convey.Convey("commit test", t, GetEntriesInRefSpec(ctx, urlStr)) } From bf8730226c56f73877bfa6e74f35eab7a03749ba Mon Sep 17 00:00:00 2001 From: brown Date: Tue, 26 Dec 2023 11:49:32 +0800 Subject: [PATCH 125/210] chore: remove comments --- integrationtest/root_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/integrationtest/root_test.go b/integrationtest/root_test.go index c9b48a1e..41017f7a 100644 --- a/integrationtest/root_test.go +++ b/integrationtest/root_test.go @@ -14,9 +14,9 @@ func TestSpec(t *testing.T) { convey.Convey("user test", t, UserSpec(ctx, urlStr)) convey.Convey("repo test", t, RepoSpec(ctx, urlStr)) - // convey.Convey("branch test", t, BranchSpec(ctx, urlStr)) - // convey.Convey("wip test", t, WipSpec(ctx, urlStr)) - // convey.Convey("object test", t, ObjectSpec(ctx, urlStr)) - // convey.Convey("wip object test", t, WipObjectSpec(ctx, urlStr)) - // convey.Convey("commit test", t, GetEntriesInRefSpec(ctx, urlStr)) + convey.Convey("branch test", t, BranchSpec(ctx, urlStr)) + convey.Convey("wip test", t, WipSpec(ctx, urlStr)) + convey.Convey("object test", t, ObjectSpec(ctx, urlStr)) + convey.Convey("wip object test", t, WipObjectSpec(ctx, urlStr)) + convey.Convey("commit test", t, GetEntriesInRefSpec(ctx, urlStr)) } From be5983c254fe9a2081798e63da97a05393b2aff5 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Thu, 28 Dec 2023 19:18:16 +0800 Subject: [PATCH 126/210] test: add test for cookie and jwt --- controller/user_ctl.go | 1 - integrationtest/branch_test.go | 2 +- integrationtest/commit_test.go | 2 +- integrationtest/helper_test.go | 14 ++++++++++---- integrationtest/objects_test.go | 2 +- integrationtest/repo_test.go | 2 +- integrationtest/user_test.go | 10 +++++++++- integrationtest/wip_object_test.go | 2 +- integrationtest/wip_test.go | 2 +- 9 files changed, 25 insertions(+), 12 deletions(-) diff --git a/controller/user_ctl.go b/controller/user_ctl.go index b7cc082c..a4489d9b 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -33,7 +33,6 @@ type UserController struct { } func (userCtl UserController) Login(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, body api.LoginJSONRequestBody) { - // get user encryptedPassword by username ep, err := userCtl.Repo.UserRepo().GetEPByName(ctx, body.Name) if err != nil { diff --git a/integrationtest/branch_test.go b/integrationtest/branch_test.go index 3b9dc86d..3fda89f8 100644 --- a/integrationtest/branch_test.go +++ b/integrationtest/branch_test.go @@ -23,7 +23,7 @@ func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { prefix := "feat/" createUser(ctx, c, client, userName) - loginAndSwitch(ctx, c, client, userName) + loginAndSwitch(ctx, c, client, "mike login", userName, false) createRepo(ctx, c, client, repoName) c.Convey("create branch", func(c convey.C) { diff --git a/integrationtest/commit_test.go b/integrationtest/commit_test.go index 10fbbfc6..0e4c3d3a 100644 --- a/integrationtest/commit_test.go +++ b/integrationtest/commit_test.go @@ -19,7 +19,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { branchName := "feat/get_entries_test" createUser(ctx, c, client, userName) - loginAndSwitch(ctx, c, client, userName) + loginAndSwitch(ctx, c, client, "kitty login", userName, false) createRepo(ctx, c, client, repoName) createBranch(ctx, c, client, userName, repoName, "main", branchName) createWip(ctx, c, client, "feat get entries test0", userName, repoName, branchName) diff --git a/integrationtest/helper_test.go b/integrationtest/helper_test.go index 361acb02..d4d059b3 100644 --- a/integrationtest/helper_test.go +++ b/integrationtest/helper_test.go @@ -114,19 +114,25 @@ func createUser(ctx context.Context, c convey.C, client *api.Client, userName st }) } -func loginAndSwitch(ctx context.Context, c convey.C, client *api.Client, userName string) { - c.Convey("login "+userName, func() { +func loginAndSwitch(ctx context.Context, c convey.C, client *api.Client, title, userName string, useCookie bool) { + c.Convey("login "+title, func() { resp, err := client.Login(ctx, api.LoginJSONRequestBody{ Name: userName, Password: "12345678", }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + loginResult, err := api.ParseLoginResponse(resp) + convey.So(err, convey.ShouldBeNil) client.RequestEditors = nil client.RequestEditors = append(client.RequestEditors, func(ctx context.Context, req *http.Request) error { - for _, cookie := range resp.Cookies() { - req.AddCookie(cookie) + if useCookie { + for _, cookie := range resp.Cookies() { + req.AddCookie(cookie) + } + } else { + req.Header.Add("Authorization", "Bearer "+loginResult.JSON200.Token) } return nil }) diff --git a/integrationtest/objects_test.go b/integrationtest/objects_test.go index 7b1a192c..9a7153c5 100644 --- a/integrationtest/objects_test.go +++ b/integrationtest/objects_test.go @@ -22,7 +22,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { branchName := "feat/obj_test" createUser(ctx, c, client, userName) - loginAndSwitch(ctx, c, client, userName) + loginAndSwitch(ctx, c, client, "molly login", userName, false) createRepo(ctx, c, client, repoName) createBranch(ctx, c, client, userName, repoName, "main", branchName) createWip(ctx, c, client, "feat get obj test", userName, repoName, branchName) diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index a9e104d9..a56f13b6 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -19,7 +19,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { userName := "jimmy" repoName := "happyrun" createUser(ctx, c, client, userName) - loginAndSwitch(ctx, c, client, userName) + loginAndSwitch(ctx, c, client, "jimmy login", userName, false) c.Convey("create repo", func(c convey.C) { c.Convey("forbidden create repo name", func() { diff --git a/integrationtest/user_test.go b/integrationtest/user_test.go index 2ee208a3..c2392310 100644 --- a/integrationtest/user_test.go +++ b/integrationtest/user_test.go @@ -30,12 +30,20 @@ func UserSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) - loginAndSwitch(ctx, c, client, userName) + loginAndSwitch(ctx, c, client, "admin login", userName, false) c.Convey("usr profile", func() { resp, err := client.GetUserInfo(ctx) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) }) + + loginAndSwitch(ctx, c, client, "admin login again", userName, true) + + c.Convey("usr profile with cookie", func() { + resp, err := client.GetUserInfo(ctx) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + }) } } diff --git a/integrationtest/wip_object_test.go b/integrationtest/wip_object_test.go index 8c1f6172..33cde7e7 100644 --- a/integrationtest/wip_object_test.go +++ b/integrationtest/wip_object_test.go @@ -18,7 +18,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { branchName := "feat/wip_obj_test" createUser(ctx, c, client, userName) - loginAndSwitch(ctx, c, client, userName) + loginAndSwitch(ctx, c, client, "jude login", userName, false) createRepo(ctx, c, client, repoName) createBranch(ctx, c, client, userName, repoName, "main", branchName) createWip(ctx, c, client, "get wip obj test", userName, repoName, branchName) diff --git a/integrationtest/wip_test.go b/integrationtest/wip_test.go index 5f0486cd..8d65b634 100644 --- a/integrationtest/wip_test.go +++ b/integrationtest/wip_test.go @@ -18,7 +18,7 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { branchNameForDelete := "feat/wip_test2" createUser(ctx, c, client, userName) - loginAndSwitch(ctx, c, client, userName) + loginAndSwitch(ctx, c, client, "july login", userName, false) createRepo(ctx, c, client, repoName) createBranch(ctx, c, client, userName, repoName, "main", branchName) createBranch(ctx, c, client, userName, repoName, "main", branchNameForDelete) From 73847b9de68167b83c97da069a55dc40cd47a9ba Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Thu, 28 Dec 2023 21:15:38 +0800 Subject: [PATCH 127/210] feat: many fix --- README.md | 3 ++ controller/branch_ctl.go | 7 +++- controller/repository_ctl.go | 42 +++++++++++++++++--- controller/wip_ctl.go | 64 +++++++++++++++++++++++------- integrationtest/wip_object_test.go | 17 ++++++++ integrationtest/wip_test.go | 4 +- models/commit.go | 31 +++++++++++++++ models/commit_test.go | 39 ++++++++++++++++++ models/models.go | 4 +- models/tag.go | 18 +++++++++ models/tag_test.go | 39 ++++++++++++++++++ models/tree.go | 31 +++++++++++++++ models/tree_test.go | 30 ++++++++++++++ versionmgr/worktree.go | 5 ++- versionmgr/worktree_test.go | 8 ++++ 15 files changed, 314 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index e54919c8..b091d319 100644 --- a/README.md +++ b/README.md @@ -15,3 +15,6 @@ init and running ./jzfs daemon ``` + + +revert object只revert一个对象 \ No newline at end of file diff --git a/controller/branch_ctl.go b/controller/branch_ctl.go index e4af743f..d3f1077c 100644 --- a/controller/branch_ctl.go +++ b/controller/branch_ctl.go @@ -36,9 +36,14 @@ func CheckBranchName(name string) error { return fmt.Errorf("branch format must be or /") } - if !branchNameRegex.Match([]byte(seg[0])) || !branchNameRegex.Match([]byte(seg[1])) { + if !branchNameRegex.Match([]byte(seg[0])) { return fmt.Errorf("branch name must be combination of number and letter or combine with '/'") } + if len(seg) > 2 { + if !branchNameRegex.Match([]byte(seg[1])) { + return fmt.Errorf("branch name must be combination of number and letter or combine with '/'") + } + } return nil } diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index 252e30d8..0fea3506 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -273,22 +273,52 @@ func (repositoryCtl RepositoryController) DeleteRepository(ctx context.Context, return } - repo, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) + repository, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) if err != nil { w.Error(err) return } - affectRows, err := repositoryCtl.Repo.RepositoryRepo().Delete(ctx, models.NewDeleteRepoParams().SetID(repo.ID)) + err = repositoryCtl.Repo.Transaction(ctx, func(repo models.IRepo) error { + // delete repository + affectRows, err := repositoryCtl.Repo.RepositoryRepo().Delete(ctx, models.NewDeleteRepoParams().SetID(repository.ID)) + if err != nil { + return err + } + + if affectRows == 0 { + return fmt.Errorf("repo not found %w", models.ErrNotFound) + } + + //delete branch + _, err = repositoryCtl.Repo.BranchRepo().Delete(ctx, models.NewDeleteBranchParams().SetRepositoryID(repository.ID)) + if err != nil { + return err + } + + //delete commit + _, err = repositoryCtl.Repo.CommitRepo(repository.ID).Delete(ctx, models.NewDeleteParams()) + if err != nil { + return err + } + + //delete tag + _, err = repositoryCtl.Repo.TagRepo(repository.ID).Delete(ctx, models.NewDeleteParams()) + if err != nil { + return err + } + // delete tree + _, err = repositoryCtl.Repo.FileTreeRepo(repository.ID).Delete(ctx, models.NewDeleteTreeParams()) + if err != nil { + return err + } + return err + }) if err != nil { w.Error(err) return } - if affectRows == 0 { - w.NotFound() - return - } w.OK() } diff --git a/controller/wip_ctl.go b/controller/wip_ctl.go index 351635cc..3b93dc67 100644 --- a/controller/wip_ctl.go +++ b/controller/wip_ctl.go @@ -5,7 +5,6 @@ import ( "context" "encoding/hex" "errors" - "fmt" "net/http" "strings" "time" @@ -58,9 +57,9 @@ func (wipCtl WipController) CreateWip(ctx context.Context, w *api.JiaozifsRespon return } - _, err = wipCtl.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetCreatorID(operator.ID).SetRepositoryID(repository.ID).SetRefID(ref.ID)) + wip, err := wipCtl.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetCreatorID(operator.ID).SetRepositoryID(repository.ID).SetRefID(ref.ID)) if err == nil { - w.BadRequest(fmt.Sprintf("ref %s already in wip", params.RefName)) + w.JSON(wip) return } if err != nil && !errors.Is(err, models.ErrNotFound) { @@ -78,7 +77,7 @@ func (wipCtl WipController) CreateWip(ctx context.Context, w *api.JiaozifsRespon currentTreeHash = baseCommit.TreeHash } - wip := &models.WorkingInProcess{ + wip = &models.WorkingInProcess{ CurrentTree: currentTreeHash, BaseCommit: ref.CommitHash, RepositoryID: repository.ID, @@ -128,12 +127,43 @@ func (wipCtl WipController) GetWip(ctx context.Context, w *api.JiaozifsResponse, } wip, err := wipCtl.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetRefID(ref.ID).SetCreatorID(operator.ID).SetRepositoryID(repository.ID)) - if err != nil { + if err == nil { + w.JSON(wip) + return + } + + if err != nil && !errors.Is(err, models.ErrNotFound) { w.Error(err) return } - w.JSON(wip) + // if not found create a wip + currentTreeHash := hash.EmptyHash + if !ref.CommitHash.IsEmpty() { + baseCommit, err := wipCtl.Repo.CommitRepo(repository.ID).Commit(ctx, ref.CommitHash) + if err != nil { + w.Error(err) + return + } + currentTreeHash = baseCommit.TreeHash + } + + wip = &models.WorkingInProcess{ + CurrentTree: currentTreeHash, + BaseCommit: ref.CommitHash, + RepositoryID: repository.ID, + RefID: ref.ID, + State: 0, + CreatorID: operator.ID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + wip, err = wipCtl.Repo.WipRepo().Insert(ctx, wip) + if err != nil { + w.Error(err) + return + } + w.JSON(wip, http.StatusCreated) } // ListWip return wips of branches, operator only see himself wips in specific repository @@ -303,20 +333,24 @@ func (wipCtl WipController) GetWipChanges(ctx context.Context, w *api.JiaozifsRe return } - commit, err := wipCtl.Repo.CommitRepo(repository.ID).Commit(ctx, wip.BaseCommit) - if err != nil { - w.Error(err) - return + treeHash := hash.EmptyHash + if !wip.BaseCommit.IsEmpty() { + commit, err := wipCtl.Repo.CommitRepo(repository.ID).Commit(ctx, wip.BaseCommit) + if err != nil { + w.Error(err) + return + } + treeHash = commit.Hash } - workTree, err := versionmgr.NewWorkTree(ctx, wipCtl.Repo.FileTreeRepo(repository.ID), models.NewRootTreeEntry(commit.TreeHash)) - if err != nil { - w.Error(err) + if bytes.Equal(treeHash, wip.CurrentTree) { + w.JSON([]api.Change{}) //no change return nothing return } - if bytes.Equal(commit.TreeHash, wip.CurrentTree) { - w.JSON([]api.Change{}) //no change return nothing + workTree, err := versionmgr.NewWorkTree(ctx, wipCtl.Repo.FileTreeRepo(repository.ID), models.NewRootTreeEntry(treeHash)) + if err != nil { + w.Error(err) return } diff --git a/integrationtest/wip_object_test.go b/integrationtest/wip_object_test.go index 33cde7e7..9c60f63d 100644 --- a/integrationtest/wip_object_test.go +++ b/integrationtest/wip_object_test.go @@ -295,7 +295,24 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { uploadObject(ctx, c, client, "update f5 to test branch", userName, repoName, branchName, "b.dat") uploadObject(ctx, c, client, "update f6 to test branch", userName, repoName, branchName, "c.dat") + testBranchName := "test/empty_branch" + createBranch(ctx, c, client, userName, repoName, "main", testBranchName) + createWip(ctx, c, client, "create empty_branch wip", userName, repoName, testBranchName) + + c.Convey("get wip success on init", func(c convey.C) { + resp, err := client.GetWipChanges(ctx, userName, repoName, &api.GetWipChangesParams{ + RefName: testBranchName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseGetWipChangesResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(*result.JSON200, convey.ShouldHaveLength, 0) + }) + c.Convey("get wip changes", func(c convey.C) { + c.Convey("no auth", func() { re := client.RequestEditors client.RequestEditors = nil diff --git a/integrationtest/wip_test.go b/integrationtest/wip_test.go index 8d65b634..63483d37 100644 --- a/integrationtest/wip_test.go +++ b/integrationtest/wip_test.go @@ -240,8 +240,8 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { } } -func createWip(ctx context.Context, c convey.C, client *api.Client, random string, user string, repoName string, refName string) { - c.Convey("create wip "+random, func() { +func createWip(ctx context.Context, c convey.C, client *api.Client, title string, user string, repoName string, refName string) { + c.Convey("create wip "+title, func() { resp, err := client.CreateWip(ctx, user, repoName, &api.CreateWipParams{ RefName: refName, }) diff --git a/models/commit.go b/models/commit.go index 9183525d..43cdf796 100644 --- a/models/commit.go +++ b/models/commit.go @@ -108,10 +108,24 @@ func (commit *Commit) NumParents() int { return len(commit.ParentHashes) } +type DeleteParams struct { + hash hash.Hash +} + +func NewDeleteParams() *DeleteParams { + return &DeleteParams{} +} + +func (params *DeleteParams) SetHash(hash hash.Hash) *DeleteParams { + params.hash = hash + return params +} + type ICommitRepo interface { RepositoryID() uuid.UUID Commit(ctx context.Context, hash hash.Hash) (*Commit, error) Insert(ctx context.Context, commit *Commit) (*Commit, error) + Delete(ctx context.Context, params *DeleteParams) (int64, error) } type CommitRepo struct { db bun.IDB @@ -150,3 +164,20 @@ func (cr CommitRepo) Insert(ctx context.Context, commit *Commit) (*Commit, error } return commit, nil } + +func (cr CommitRepo) Delete(ctx context.Context, params *DeleteParams) (int64, error) { + query := cr.db.NewDelete().Model((*Commit)(nil)).Where("repository_id = ?", cr.repositoryID) + if params.hash != nil { + query = query.Where("hash = ?", params.hash) + } + + sqlResult, err := query.Exec(ctx) + if err != nil { + return 0, err + } + affectedRows, err := sqlResult.RowsAffected() + if err != nil { + return 0, err + } + return affectedRows, err +} diff --git a/models/commit_test.go b/models/commit_test.go index e56619ca..ea27bb8e 100644 --- a/models/commit_test.go +++ b/models/commit_test.go @@ -38,3 +38,42 @@ func TestCommitRepo(t *testing.T) { require.ErrorIs(t, err, models.ErrRepoIDMisMatch) }) } + +func TestDeleteCOmmit(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + t.Run("delete commit", func(t *testing.T) { + repoID := uuid.New() + commitRepo := models.NewCommitRepo(db, repoID) + require.Equal(t, commitRepo.RepositoryID(), repoID) + + toDeleteModel := &models.Commit{} + require.NoError(t, gofakeit.Struct(toDeleteModel)) + toDeleteModel.RepositoryID = repoID + toDeleteModel, err := commitRepo.Insert(ctx, toDeleteModel) + require.NoError(t, err) + + affectRows, err := commitRepo.Delete(ctx, models.NewDeleteParams().SetHash(toDeleteModel.Hash)) + require.NoError(t, err) + require.Equal(t, int64(1), affectRows) + }) + + t.Run("delete batch", func(t *testing.T) { + repoID := uuid.New() + commitRepo := models.NewCommitRepo(db, repoID) + require.Equal(t, commitRepo.RepositoryID(), repoID) + + for i := 0; i < 5; i++ { + toDeleteModel := &models.Commit{} + require.NoError(t, gofakeit.Struct(toDeleteModel)) + toDeleteModel.RepositoryID = repoID + toDeleteModel, err := commitRepo.Insert(ctx, toDeleteModel) + require.NoError(t, err) + } + + affectRows, err := commitRepo.Delete(ctx, models.NewDeleteParams()) + require.NoError(t, err) + require.Equal(t, int64(5), affectRows) + }) +} diff --git a/models/models.go b/models/models.go index 6042c0f1..bb73b275 100644 --- a/models/models.go +++ b/models/models.go @@ -21,9 +21,7 @@ func SetupDatabase(ctx context.Context, lc fx.Lifecycle, dbConfig *config.Databa bunDB := bun.NewDB(sqlDB, pgdialect.New(), bun.WithDiscardUnknownColumns()) - if dbConfig.Debug { - bunDB.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true))) - } + bunDB.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true))) lc.Append(fx.Hook{ OnStop: func(ctx context.Context) error { diff --git a/models/tag.go b/models/tag.go index f7e8209d..97a4a743 100644 --- a/models/tag.go +++ b/models/tag.go @@ -34,6 +34,7 @@ type ITagRepo interface { RepositoryID() uuid.UUID Insert(ctx context.Context, tag *Tag) (*Tag, error) Tag(ctx context.Context, hash hash.Hash) (*Tag, error) + Delete(ctx context.Context, params *DeleteParams) (int64, error) } type TagRepo struct { @@ -74,3 +75,20 @@ func (t *TagRepo) Tag(ctx context.Context, hash hash.Hash) (*Tag, error) { } return tag, nil } + +func (t *TagRepo) Delete(ctx context.Context, params *DeleteParams) (int64, error) { + query := t.db.NewDelete().Model((*Tag)(nil)).Where("repository_id = ?", t.repositoryID) + if params.hash != nil { + query = query.Where("hash = ?", params.hash) + } + + sqlResult, err := query.Exec(ctx) + if err != nil { + return 0, err + } + affectedRows, err := sqlResult.RowsAffected() + if err != nil { + return 0, err + } + return affectedRows, err +} diff --git a/models/tag_test.go b/models/tag_test.go index b81fc4c6..e78cbe50 100644 --- a/models/tag_test.go +++ b/models/tag_test.go @@ -38,3 +38,42 @@ func TestTagRepo(t *testing.T) { require.ErrorIs(t, err, models.ErrRepoIDMisMatch) }) } + +func TestDeleteTag(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + t.Run("delete tag", func(t *testing.T) { + repoID := uuid.New() + tagRepo := models.NewTagRepo(db, repoID) + require.Equal(t, tagRepo.RepositoryID(), repoID) + + toDeleteModel := &models.Tag{} + require.NoError(t, gofakeit.Struct(toDeleteModel)) + toDeleteModel.RepositoryID = repoID + toDeleteModel, err := tagRepo.Insert(ctx, toDeleteModel) + require.NoError(t, err) + + affectRows, err := tagRepo.Delete(ctx, models.NewDeleteParams().SetHash(toDeleteModel.Hash)) + require.NoError(t, err) + require.Equal(t, int64(1), affectRows) + }) + + t.Run("delete tags batch", func(t *testing.T) { + repoID := uuid.New() + tagRepo := models.NewTagRepo(db, repoID) + require.Equal(t, tagRepo.RepositoryID(), repoID) + + for i := 0; i < 5; i++ { + toDeleteModel := &models.Tag{} + require.NoError(t, gofakeit.Struct(toDeleteModel)) + toDeleteModel.RepositoryID = repoID + toDeleteModel, err := tagRepo.Insert(ctx, toDeleteModel) + require.NoError(t, err) + } + + affectRows, err := tagRepo.Delete(ctx, models.NewDeleteParams()) + require.NoError(t, err) + require.Equal(t, int64(5), affectRows) + }) +} diff --git a/models/tree.go b/models/tree.go index 551bfecf..02c7563e 100644 --- a/models/tree.go +++ b/models/tree.go @@ -274,6 +274,19 @@ func (gop *GetObjParams) SetHash(hash hash.Hash) *GetObjParams { return gop } +type DeleteTreeParams struct { + hash hash.Hash +} + +func NewDeleteTreeParams() *DeleteTreeParams { + return &DeleteTreeParams{} +} + +func (dtp *DeleteTreeParams) SetHash(hash hash.Hash) *DeleteTreeParams { + dtp.hash = hash + return dtp +} + type IFileTreeRepo interface { RepositoryID() uuid.UUID Insert(ctx context.Context, repo *FileTree) (*FileTree, error) @@ -282,6 +295,7 @@ type IFileTreeRepo interface { List(ctx context.Context) ([]FileTree, error) Blob(ctx context.Context, hash hash.Hash) (*Blob, error) TreeNode(ctx context.Context, hash hash.Hash) (*TreeNode, error) + Delete(ctx context.Context, params *DeleteTreeParams) (int64, error) } var _ IFileTreeRepo = (*FileTreeRepo)(nil) @@ -371,3 +385,20 @@ func (o FileTreeRepo) List(ctx context.Context) ([]FileTree, error) { } return obj, nil } + +func (o FileTreeRepo) Delete(ctx context.Context, params *DeleteTreeParams) (int64, error) { + query := o.db.NewDelete().Model((*TreeNode)(nil)).Where("repository_id = ?", o.repositoryID) + if params.hash != nil { + query = query.Where("hash = ?", params.hash) + } + + sqlResult, err := query.Exec(ctx) + if err != nil { + return 0, err + } + affectedRows, err := sqlResult.RowsAffected() + if err != nil { + return 0, err + } + return affectedRows, err +} diff --git a/models/tree_test.go b/models/tree_test.go index e626368c..ecc74139 100644 --- a/models/tree_test.go +++ b/models/tree_test.go @@ -96,3 +96,33 @@ func TestNewTreeNode(t *testing.T) { require.Equal(t, "27d9fbf6d43195f34404a94c0de707a2", node.Hash.Hex()) }) } + +func TestFileTreeRepo_Delete(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + repoID := uuid.New() + repo := models.NewFileTree(db, repoID) + require.Equal(t, repo.RepositoryID(), repoID) + + var treeModels []*models.FileTree + for i := 0; i < 5; i++ { + objModel := &models.FileTree{} + require.NoError(t, gofakeit.Struct(objModel)) + objModel.RepositoryID = repoID + newModel, err := repo.Insert(ctx, objModel) + require.NoError(t, err) + treeModels = append(treeModels, newModel) + } + + //delete one + affectRows, err := repo.Delete(ctx, models.NewDeleteTreeParams().SetHash(treeModels[0].Hash)) + require.NoError(t, err) + require.Equal(t, int64(1), affectRows) + + //delete batch + affectRows, err = repo.Delete(ctx, models.NewDeleteTreeParams()) + require.NoError(t, err) + require.Equal(t, int64(4), affectRows) +} diff --git a/versionmgr/worktree.go b/versionmgr/worktree.go index b93e98cb..429a551e 100644 --- a/versionmgr/worktree.go +++ b/versionmgr/worktree.go @@ -148,7 +148,7 @@ func (workTree *WorkTree) ReplaceSubTreeEntry(ctx context.Context, treeEntry mod } func (workTree *WorkTree) matchPath(ctx context.Context, path string) ([]FullObject, []string, error) { - pathSegs := strings.Split(filepath.Clean(path), fmt.Sprintf("%c", os.PathSeparator)) + pathSegs := strings.Split(path, fmt.Sprintf("%c", os.PathSeparator)) var existNodes []FullObject var missingPath []string //a/b/c/d/e @@ -213,6 +213,9 @@ func (workTree *WorkTree) AddLeaf(ctx context.Context, fullPath string, blob *mo slices.Reverse(missingPath) var lastEntry models.TreeEntry for index, path := range missingPath { + if len(path) == 0 { //todo add validate name check + return fmt.Errorf("name is empty") + } if index == 0 { _, err = workTree.object.Insert(ctx, blob.FileTree()) if err != nil { diff --git a/versionmgr/worktree_test.go b/versionmgr/worktree_test.go index 0b27474b..33d51456 100644 --- a/versionmgr/worktree_test.go +++ b/versionmgr/worktree_test.go @@ -137,3 +137,11 @@ func TestRemoveEntry(t *testing.T) { require.NoError(t, err) require.Len(t, entries, 0) } + +func TestCleanPath(t *testing.T) { + require.Equal(t, "", CleanPath("")) + require.Equal(t, "", CleanPath("/")) + + require.Equal(t, "a/b/c", CleanPath("a/b/c")) + require.Equal(t, "a/b/c", CleanPath("/a/b/c/")) +} From 765c3686a328f376acfdab2915522336cff7e643 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Thu, 28 Dec 2023 21:31:26 +0800 Subject: [PATCH 128/210] fix: use correct tree hash --- controller/wip_ctl.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/wip_ctl.go b/controller/wip_ctl.go index 3b93dc67..b105f339 100644 --- a/controller/wip_ctl.go +++ b/controller/wip_ctl.go @@ -340,7 +340,7 @@ func (wipCtl WipController) GetWipChanges(ctx context.Context, w *api.JiaozifsRe w.Error(err) return } - treeHash = commit.Hash + treeHash = commit.TreeHash } if bytes.Equal(treeHash, wip.CurrentTree) { From 7f6fd5128b4a534020e61bd0f9a0df22375b75dd Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Thu, 28 Dec 2023 21:53:20 +0800 Subject: [PATCH 129/210] fix: remove wip when delte repo --- api/jiaozifs.gen.go | 85 +++++++++++++------------- api/swagger.yml | 2 + controller/repository_ctl.go | 20 ++++++- integrationtest/repo_test.go | 17 ++++++ integrationtest/wip_test.go | 112 ++++++++++------------------------- models/commit_test.go | 6 +- models/repository.go | 15 ++++- models/repository_test.go | 42 ++++++++++--- models/tag_test.go | 2 +- 9 files changed, 164 insertions(+), 137 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 69b8eeb7..4375168a 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -237,6 +237,7 @@ type TreeEntry struct { // UpdateRepository defines model for UpdateRepository. type UpdateRepository struct { Description *string `json:"description,omitempty"` + Head *string `json:"head,omitempty"` } // UserInfo defines model for UserInfo. @@ -6639,48 +6640,48 @@ var swaggerSpec = []string{ "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", "AAA=", } diff --git a/api/swagger.yml b/api/swagger.yml index bde7fe0f..eef2b0bb 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -202,6 +202,8 @@ components: properties: description: type: string + head: + type: string RepositoryList: type: object required: diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index 0fea3506..e0636a4a 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -312,6 +312,9 @@ func (repositoryCtl RepositoryController) DeleteRepository(ctx context.Context, if err != nil { return err } + + //delete wip + _, err = repositoryCtl.Repo.WipRepo().Delete(ctx, models.NewDeleteWipParams().SetRepositoryID(repository.ID)) return err }) if err != nil { @@ -373,7 +376,22 @@ func (repositoryCtl RepositoryController) UpdateRepository(ctx context.Context, return } - err = repositoryCtl.Repo.RepositoryRepo().UpdateByID(ctx, models.NewUpdateRepoParams(repo.ID).SetDescription(utils.StringValue(body.Description))) + params := models.NewUpdateRepoParams(repo.ID) + if body.Head != nil { + _, err = repositoryCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repo.ID).SetName(utils.StringValue(body.Head))) + if err != nil { + w.Error(err) + return + } + + params.SetHead(utils.StringValue(body.Head)) + } + + if body.Description != nil { + params.SetDescription(utils.StringValue(body.Description)) + } + + err = repositoryCtl.Repo.RepositoryRepo().UpdateByID(ctx, params) if err != nil { w.Error(err) return diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index a56f13b6..268aa515 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -342,6 +342,23 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) }) + + c.Convey("update head to not exit", func() { + resp, err := client.UpdateRepository(ctx, userName, repoName, api.UpdateRepositoryJSONRequestBody{ + Head: utils.String("xxx"), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + createBranch(ctx, c, client, userName, repoName, "main", "feat/ano_branch") + c.Convey("update default head success", func() { + resp, err := client.UpdateRepository(ctx, userName, repoName, api.UpdateRepositoryJSONRequestBody{ + Head: utils.String("feat/ano_branch"), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + }) }) c.Convey("get commits in repository", func(c convey.C) { diff --git a/integrationtest/wip_test.go b/integrationtest/wip_test.go index 63483d37..c2c09b6e 100644 --- a/integrationtest/wip_test.go +++ b/integrationtest/wip_test.go @@ -32,11 +32,13 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(respResult.JSON200, convey.ShouldHaveLength, 0) }) - c.Convey("create wip", func(c convey.C) { + createWip(ctx, c, client, "create main wip", userName, repoName, "main") + + c.Convey("get wip", func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors client.RequestEditors = nil - resp, err := client.CreateWip(ctx, userName, repoName, &api.CreateWipParams{ + resp, err := client.GetWip(ctx, userName, repoName, &api.GetWipParams{ RefName: branchName, }) client.RequestEditors = re @@ -44,57 +46,61 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) - c.Convey("fail to create branch in non exit repo", func() { - resp, err := client.CreateWip(ctx, userName, "fakerepo", &api.CreateWipParams{ + c.Convey("auto create a wip", func() { + resp, err := client.GetWip(ctx, userName, repoName, &api.GetWipParams{ RefName: branchName, }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + + _, err = api.ParseGetWipResponse(resp) + convey.So(err, convey.ShouldBeNil) }) - c.Convey("fail to create branch in non exit user", func() { - resp, err := client.CreateWip(ctx, "mock_user", "main", &api.CreateWipParams{ + c.Convey("success get wip", func() { + resp, err := client.GetWip(ctx, userName, repoName, &api.GetWipParams{ RefName: branchName, }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + _, err = api.ParseGetWipResponse(resp) + convey.So(err, convey.ShouldBeNil) }) - c.Convey("fail to create branch in non exit ref", func() { - resp, err := client.CreateWip(ctx, userName, repoName, &api.CreateWipParams{ - RefName: "mock ref", + c.Convey("fail to get wip in non exit ref", func() { + resp, err := client.GetWip(ctx, userName, repoName, &api.GetWipParams{ + RefName: "mock_ref", }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) }) - c.Convey("forbidden create branch in others", func() { - resp, err := client.CreateWip(ctx, "jimmy", "happygo", &api.CreateWipParams{ - RefName: "main", + c.Convey("fail to get wip from non exit user", func() { + resp, err := client.GetWip(ctx, "mock_owner", repoName, &api.GetWipParams{ + RefName: branchName, }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) }) - c.Convey("success create branch", func() { - resp, err := client.CreateWip(ctx, userName, repoName, &api.CreateWipParams{ + c.Convey("fail to get non exit branch", func() { + resp, err := client.GetWip(ctx, userName, "mock_repo", &api.GetWipParams{ RefName: branchName, }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) }) - c.Convey("user only have one wip for one userName", func() { - resp, err := client.CreateWip(ctx, userName, repoName, &api.CreateWipParams{ - RefName: branchName, + c.Convey("fail to others repo's wips", func() { + resp, err := client.GetWip(ctx, "jimmy", "happygo", &api.GetWipParams{ + RefName: "main", }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) }) }) - createWip(ctx, c, client, "create main wip", userName, repoName, "main") - c.Convey("list wip", func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors @@ -134,62 +140,6 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { }) }) - c.Convey("get wip", func(c convey.C) { - c.Convey("no auth", func() { - re := client.RequestEditors - client.RequestEditors = nil - resp, err := client.GetWip(ctx, userName, repoName, &api.GetWipParams{ - RefName: branchName, - }) - client.RequestEditors = re - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) - }) - - c.Convey("success get branch", func() { - resp, err := client.GetWip(ctx, userName, repoName, &api.GetWipParams{ - RefName: branchName, - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) - - _, err = api.ParseGetWipResponse(resp) - convey.So(err, convey.ShouldBeNil) - }) - - c.Convey("fail to get wip in non exit ref", func() { - resp, err := client.GetWip(ctx, userName, repoName, &api.GetWipParams{ - RefName: "mock_ref", - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) - }) - - c.Convey("fail to get wip from non exit user", func() { - resp, err := client.GetWip(ctx, "mock_owner", repoName, &api.GetWipParams{ - RefName: branchName, - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) - }) - - c.Convey("fail to get non exit branch", func() { - resp, err := client.GetWip(ctx, userName, "mock_repo", &api.GetWipParams{ - RefName: branchName, - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) - }) - - c.Convey("fail to others repo's wips", func() { - resp, err := client.GetWip(ctx, "jimmy", "happygo", &api.GetWipParams{ - RefName: "main", - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) - }) - }) - c.Convey("delete wip", func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors @@ -234,7 +184,7 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { //ensure delete work getResp, err := client.GetWip(ctx, userName, repoName, &api.GetWipParams{RefName: branchNameForDelete}) convey.So(err, convey.ShouldBeNil) - convey.So(getResp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + convey.So(getResp.StatusCode, convey.ShouldEqual, http.StatusCreated) }) }) } diff --git a/models/commit_test.go b/models/commit_test.go index ea27bb8e..ee36d139 100644 --- a/models/commit_test.go +++ b/models/commit_test.go @@ -39,7 +39,7 @@ func TestCommitRepo(t *testing.T) { }) } -func TestDeleteCOmmit(t *testing.T) { +func TestDeleteCommit(t *testing.T) { ctx := context.Background() postgres, _, db := testhelper.SetupDatabase(ctx, t) defer postgres.Stop() //nolint @@ -47,7 +47,6 @@ func TestDeleteCOmmit(t *testing.T) { repoID := uuid.New() commitRepo := models.NewCommitRepo(db, repoID) require.Equal(t, commitRepo.RepositoryID(), repoID) - toDeleteModel := &models.Commit{} require.NoError(t, gofakeit.Struct(toDeleteModel)) toDeleteModel.RepositoryID = repoID @@ -63,12 +62,11 @@ func TestDeleteCOmmit(t *testing.T) { repoID := uuid.New() commitRepo := models.NewCommitRepo(db, repoID) require.Equal(t, commitRepo.RepositoryID(), repoID) - for i := 0; i < 5; i++ { toDeleteModel := &models.Commit{} require.NoError(t, gofakeit.Struct(toDeleteModel)) toDeleteModel.RepositoryID = repoID - toDeleteModel, err := commitRepo.Insert(ctx, toDeleteModel) + _, err := commitRepo.Insert(ctx, toDeleteModel) require.NoError(t, err) } diff --git a/models/repository.go b/models/repository.go index cbcef063..b68cd89b 100644 --- a/models/repository.go +++ b/models/repository.go @@ -131,6 +131,7 @@ type UpdateRepoParams struct { bun.BaseModel `bun:"table:repositories"` ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` Description *string `bun:"description"` + HEAD *string `bun:"head,notnull"` } func NewUpdateRepoParams(id uuid.UUID) *UpdateRepoParams { @@ -144,6 +145,11 @@ func (up *UpdateRepoParams) SetDescription(description string) *UpdateRepoParams return up } +func (up *UpdateRepoParams) SetHead(head string) *UpdateRepoParams { + up.HEAD = &head + return up +} + type IRepositoryRepo interface { Insert(ctx context.Context, repo *Repository) (*Repository, error) Get(ctx context.Context, params *GetRepoParams) (*Repository, error) @@ -258,6 +264,13 @@ func (r *RepositoryRepo) Delete(ctx context.Context, params *DeleteRepoParams) ( } func (r *RepositoryRepo) UpdateByID(ctx context.Context, updateModel *UpdateRepoParams) error { - _, err := r.db.NewUpdate().Model(updateModel).WherePK().Exec(ctx) + updateQuery := r.db.NewUpdate().Model((*Repository)(nil)).Where("id = ?", updateModel.ID) + if updateModel.Description != nil { + updateQuery.Set("description = ?", *updateModel.Description) + } + if updateModel.HEAD != nil { + updateQuery.Set("head = ?", *updateModel.HEAD) + } + _, err := updateQuery.Exec(ctx) return err } diff --git a/models/repository_test.go b/models/repository_test.go index 418f9afe..33538e30 100644 --- a/models/repository_test.go +++ b/models/repository_test.go @@ -13,7 +13,41 @@ import ( "github.com/stretchr/testify/require" ) -func TestRepositoryRepo_Insert(t *testing.T) { +func TestRepositoryUpdate(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + repo := models.NewRepositoryRepo(db) + + t.Run("only update desc", func(t *testing.T) { + repoModel := &models.Repository{} + require.NoError(t, gofakeit.Struct(repoModel)) + newRepo, err := repo.Insert(ctx, repoModel) + require.NoError(t, err) + err = repo.UpdateByID(ctx, models.NewUpdateRepoParams(newRepo.ID).SetDescription("description")) + require.NoError(t, err) + user, err := repo.Get(ctx, models.NewGetRepoParams().SetID(newRepo.ID)) + require.NoError(t, err) + require.Equal(t, "description", *user.Description) + require.Equal(t, newRepo.HEAD, user.HEAD) + }) + + t.Run("update all fields", func(t *testing.T) { + repoModel := &models.Repository{} + require.NoError(t, gofakeit.Struct(repoModel)) + newRepo, err := repo.Insert(ctx, repoModel) + require.NoError(t, err) + err = repo.UpdateByID(ctx, models.NewUpdateRepoParams(newRepo.ID).SetDescription("description").SetHead("ggg")) + require.NoError(t, err) + user, err := repo.Get(ctx, models.NewGetRepoParams().SetID(newRepo.ID)) + require.NoError(t, err) + require.Equal(t, "description", *user.Description) + require.Equal(t, "ggg", user.HEAD) + }) +} + +func TestRepositoryRepoInsert(t *testing.T) { ctx := context.Background() postgres, _, db := testhelper.SetupDatabase(ctx, t) defer postgres.Stop() //nolint @@ -31,12 +65,6 @@ func TestRepositoryRepo_Insert(t *testing.T) { require.NoError(t, err) require.True(t, cmp.Equal(repoModel, user, dbTimeCmpOpt)) - err = repo.UpdateByID(ctx, models.NewUpdateRepoParams(newRepo.ID).SetDescription("description")) - require.NoError(t, err) - user, err = repo.Get(ctx, models.NewGetRepoParams().SetID(newRepo.ID)) - require.NoError(t, err) - - require.Equal(t, "description", *user.Description) //insert secondary secModel := &models.Repository{} require.NoError(t, gofakeit.Struct(secModel)) diff --git a/models/tag_test.go b/models/tag_test.go index e78cbe50..f3744521 100644 --- a/models/tag_test.go +++ b/models/tag_test.go @@ -68,7 +68,7 @@ func TestDeleteTag(t *testing.T) { toDeleteModel := &models.Tag{} require.NoError(t, gofakeit.Struct(toDeleteModel)) toDeleteModel.RepositoryID = repoID - toDeleteModel, err := tagRepo.Insert(ctx, toDeleteModel) + _, err := tagRepo.Insert(ctx, toDeleteModel) require.NoError(t, err) } From 6f980d7c2e866195bd01e01a297760b7ab58fbd3 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Fri, 29 Dec 2023 14:11:53 +0800 Subject: [PATCH 130/210] feat: add size and time field --- api/jiaozifs.gen.go | 542 ++++++++++++++++++++-------------------- api/swagger.yml | 60 ++++- controller/wip_ctl.go | 115 ++++----- models/wip.go | 4 +- models/wip_test.go | 60 +++-- versionmgr/work_repo.go | 20 ++ versionmgr/worktree.go | 46 +++- 7 files changed, 463 insertions(+), 384 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 4375168a..5b43ffba 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -122,6 +122,16 @@ type CreateRepository struct { Name string `json:"name"` } +// FullTreeEntry defines model for FullTreeEntry. +type FullTreeEntry struct { + CreatedAt time.Time `json:"created_at"` + Hash string `json:"hash"` + IsDir bool `json:"is_dir"` + Name string `json:"name"` + Size int64 `json:"size"` + UpdatedAt time.Time `json:"updated_at"` +} + // LoginConfig defines model for LoginConfig. type LoginConfig struct { // RBAC RBAC will remain enabled on GUI if "external". That only works @@ -227,13 +237,6 @@ type Signature struct { When time.Time `json:"when"` } -// TreeEntry defines model for TreeEntry. -type TreeEntry struct { - Hash string `json:"hash"` - IsDir bool `json:"is_dir"` - Name string `json:"name"` -} - // UpdateRepository defines model for UpdateRepository. type UpdateRepository struct { Description *string `json:"description,omitempty"` @@ -446,12 +449,6 @@ type GetWipParams struct { RefName string `form:"refName" json:"refName"` } -// CreateWipParams defines parameters for CreateWip. -type CreateWipParams struct { - // RefName ref name - RefName string `form:"refName" json:"refName"` -} - // GetWipChangesParams defines parameters for GetWipChanges. type GetWipChangesParams struct { // RefName ref name @@ -470,6 +467,12 @@ type CommitWipParams struct { RefName string `form:"refName" json:"refName"` } +// RevertWipParams defines parameters for RevertWip. +type RevertWipParams struct { + // RefName ref name + RefName string `form:"refName" json:"refName"` +} + // LoginJSONRequestBody defines body for Login for application/json ContentType. type LoginJSONRequestBody LoginJSONBody @@ -646,9 +649,6 @@ type ClientInterface interface { // GetWip request GetWip(ctx context.Context, owner string, repository string, params *GetWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) - // CreateWip request - CreateWip(ctx context.Context, owner string, repository string, params *CreateWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) - // GetWipChanges request GetWipChanges(ctx context.Context, owner string, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -657,6 +657,9 @@ type ClientInterface interface { // ListWip request ListWip(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // RevertWip request + RevertWip(ctx context.Context, owner string, repository string, params *RevertWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) } func (c *Client) LoginWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -1019,8 +1022,8 @@ func (c *Client) GetWip(ctx context.Context, owner string, repository string, pa return c.Client.Do(req) } -func (c *Client) CreateWip(ctx context.Context, owner string, repository string, params *CreateWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateWipRequest(c.Server, owner, repository, params) +func (c *Client) GetWipChanges(ctx context.Context, owner string, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetWipChangesRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -1031,8 +1034,8 @@ func (c *Client) CreateWip(ctx context.Context, owner string, repository string, return c.Client.Do(req) } -func (c *Client) GetWipChanges(ctx context.Context, owner string, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetWipChangesRequest(c.Server, owner, repository, params) +func (c *Client) CommitWip(ctx context.Context, owner string, repository string, params *CommitWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCommitWipRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -1043,8 +1046,8 @@ func (c *Client) GetWipChanges(ctx context.Context, owner string, repository str return c.Client.Do(req) } -func (c *Client) CommitWip(ctx context.Context, owner string, repository string, params *CommitWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCommitWipRequest(c.Server, owner, repository, params) +func (c *Client) ListWip(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListWipRequest(c.Server, owner, repository) if err != nil { return nil, err } @@ -1055,8 +1058,8 @@ func (c *Client) CommitWip(ctx context.Context, owner string, repository string, return c.Client.Do(req) } -func (c *Client) ListWip(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewListWipRequest(c.Server, owner, repository) +func (c *Client) RevertWip(ctx context.Context, owner string, repository string, params *RevertWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRevertWipRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -2581,65 +2584,6 @@ func NewGetWipRequest(server string, owner string, repository string, params *Ge return req, nil } -// NewCreateWipRequest generates requests for CreateWip -func NewCreateWipRequest(server string, owner string, repository string, params *CreateWipParams) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) - if err != nil { - return nil, err - } - - var pathParam1 string - - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/wip/%s/%s", pathParam0, pathParam1) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - if params != nil { - queryValues := queryURL.Query() - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - queryURL.RawQuery = queryValues.Encode() - } - - req, err := http.NewRequest("POST", queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - // NewGetWipChangesRequest generates requests for GetWipChanges func NewGetWipChangesRequest(server string, owner string, repository string, params *GetWipChangesParams) (*http.Request, error) { var err error @@ -2827,6 +2771,65 @@ func NewListWipRequest(server string, owner string, repository string) (*http.Re return req, nil } +// NewRevertWipRequest generates requests for RevertWip +func NewRevertWipRequest(server string, owner string, repository string, params *RevertWipParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/wip/%s/%s/revert", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("POST", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { for _, r := range c.RequestEditors { if err := r(ctx, req); err != nil { @@ -2955,9 +2958,6 @@ type ClientWithResponsesInterface interface { // GetWipWithResponse request GetWipWithResponse(ctx context.Context, owner string, repository string, params *GetWipParams, reqEditors ...RequestEditorFn) (*GetWipResponse, error) - // CreateWipWithResponse request - CreateWipWithResponse(ctx context.Context, owner string, repository string, params *CreateWipParams, reqEditors ...RequestEditorFn) (*CreateWipResponse, error) - // GetWipChangesWithResponse request GetWipChangesWithResponse(ctx context.Context, owner string, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*GetWipChangesResponse, error) @@ -2966,6 +2966,9 @@ type ClientWithResponsesInterface interface { // ListWipWithResponse request ListWipWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*ListWipResponse, error) + + // RevertWipWithResponse request + RevertWipWithResponse(ctx context.Context, owner string, repository string, params *RevertWipParams, reqEditors ...RequestEditorFn) (*RevertWipResponse, error) } type LoginResponse struct { @@ -3294,7 +3297,7 @@ func (r GetCommitDiffResponse) StatusCode() int { type GetEntriesInRefResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *[]TreeEntry + JSON200 *[]FullTreeEntry } // Status returns HTTPResponse.Status @@ -3509,14 +3512,14 @@ func (r GetWipResponse) StatusCode() int { return 0 } -type CreateWipResponse struct { +type GetWipChangesResponse struct { Body []byte HTTPResponse *http.Response - JSON201 *Wip + JSON200 *[]Change } // Status returns HTTPResponse.Status -func (r CreateWipResponse) Status() string { +func (r GetWipChangesResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3524,21 +3527,21 @@ func (r CreateWipResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r CreateWipResponse) StatusCode() int { +func (r GetWipChangesResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetWipChangesResponse struct { +type CommitWipResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *[]Change + JSON200 *Wip } // Status returns HTTPResponse.Status -func (r GetWipChangesResponse) Status() string { +func (r CommitWipResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3546,21 +3549,21 @@ func (r GetWipChangesResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetWipChangesResponse) StatusCode() int { +func (r CommitWipResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type CommitWipResponse struct { +type ListWipResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *Wip + JSON200 *[]Wip } // Status returns HTTPResponse.Status -func (r CommitWipResponse) Status() string { +func (r ListWipResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3568,21 +3571,20 @@ func (r CommitWipResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r CommitWipResponse) StatusCode() int { +func (r ListWipResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type ListWipResponse struct { +type RevertWipResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *[]Wip } // Status returns HTTPResponse.Status -func (r ListWipResponse) Status() string { +func (r RevertWipResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3590,7 +3592,7 @@ func (r ListWipResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r ListWipResponse) StatusCode() int { +func (r RevertWipResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -3862,15 +3864,6 @@ func (c *ClientWithResponses) GetWipWithResponse(ctx context.Context, owner stri return ParseGetWipResponse(rsp) } -// CreateWipWithResponse request returning *CreateWipResponse -func (c *ClientWithResponses) CreateWipWithResponse(ctx context.Context, owner string, repository string, params *CreateWipParams, reqEditors ...RequestEditorFn) (*CreateWipResponse, error) { - rsp, err := c.CreateWip(ctx, owner, repository, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseCreateWipResponse(rsp) -} - // GetWipChangesWithResponse request returning *GetWipChangesResponse func (c *ClientWithResponses) GetWipChangesWithResponse(ctx context.Context, owner string, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*GetWipChangesResponse, error) { rsp, err := c.GetWipChanges(ctx, owner, repository, params, reqEditors...) @@ -3898,6 +3891,15 @@ func (c *ClientWithResponses) ListWipWithResponse(ctx context.Context, owner str return ParseListWipResponse(rsp) } +// RevertWipWithResponse request returning *RevertWipResponse +func (c *ClientWithResponses) RevertWipWithResponse(ctx context.Context, owner string, repository string, params *RevertWipParams, reqEditors ...RequestEditorFn) (*RevertWipResponse, error) { + rsp, err := c.RevertWip(ctx, owner, repository, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseRevertWipResponse(rsp) +} + // ParseLoginResponse parses an HTTP response from a LoginWithResponse call func ParseLoginResponse(rsp *http.Response) (*LoginResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -4233,7 +4235,7 @@ func ParseGetEntriesInRefResponse(rsp *http.Response) (*GetEntriesInRefResponse, switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []TreeEntry + var dest []FullTreeEntry if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -4458,32 +4460,6 @@ func ParseGetWipResponse(rsp *http.Response) (*GetWipResponse, error) { return response, nil } -// ParseCreateWipResponse parses an HTTP response from a CreateWipWithResponse call -func ParseCreateWipResponse(rsp *http.Response) (*CreateWipResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &CreateWipResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest Wip - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON201 = &dest - - } - - return response, nil -} - // ParseGetWipChangesResponse parses an HTTP response from a GetWipChangesWithResponse call func ParseGetWipChangesResponse(rsp *http.Response) (*GetWipChangesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -4562,6 +4538,22 @@ func ParseListWipResponse(rsp *http.Response) (*ListWipResponse, error) { return response, nil } +// ParseRevertWipResponse parses an HTTP response from a RevertWipWithResponse call +func ParseRevertWipResponse(rsp *http.Response) (*RevertWipResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &RevertWipResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + // ServerInterface represents all server handlers. type ServerInterface interface { // perform a login @@ -4639,9 +4631,6 @@ type ServerInterface interface { // get working in process // (GET /wip/{owner}/{repository}) GetWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetWipParams) - // create working in process - // (POST /wip/{owner}/{repository}) - CreateWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params CreateWipParams) // get working in process changes // (GET /wip/{owner}/{repository}/changes) GetWipChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetWipChangesParams) @@ -4651,6 +4640,9 @@ type ServerInterface interface { // list wip in specific project and user // (GET /wip/{owner}/{repository}/list) ListWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) + // revert working in process + // (POST /wip/{owner}/{repository}/revert) + RevertWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params RevertWipParams) } // Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. @@ -4806,12 +4798,6 @@ func (_ Unimplemented) GetWip(ctx context.Context, w *JiaozifsResponse, r *http. w.WriteHeader(http.StatusNotImplemented) } -// create working in process -// (POST /wip/{owner}/{repository}) -func (_ Unimplemented) CreateWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params CreateWipParams) { - w.WriteHeader(http.StatusNotImplemented) -} - // get working in process changes // (GET /wip/{owner}/{repository}/changes) func (_ Unimplemented) GetWipChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetWipChangesParams) { @@ -4830,6 +4816,12 @@ func (_ Unimplemented) ListWip(ctx context.Context, w *JiaozifsResponse, r *http w.WriteHeader(http.StatusNotImplemented) } +// revert working in process +// (POST /wip/{owner}/{repository}/revert) +func (_ Unimplemented) RevertWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params RevertWipParams) { + w.WriteHeader(http.StatusNotImplemented) +} + // ServerInterfaceWrapper converts contexts to parameters. type ServerInterfaceWrapper struct { Handler ServerInterface @@ -6161,8 +6153,8 @@ func (siw *ServerInterfaceWrapper) GetWip(w http.ResponseWriter, r *http.Request handler.ServeHTTP(w, r.WithContext(ctx)) } -// CreateWip operation middleware -func (siw *ServerInterfaceWrapper) CreateWip(w http.ResponseWriter, r *http.Request) { +// GetWipChanges operation middleware +func (siw *ServerInterfaceWrapper) GetWipChanges(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -6192,7 +6184,7 @@ func (siw *ServerInterfaceWrapper) CreateWip(w http.ResponseWriter, r *http.Requ ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context - var params CreateWipParams + var params GetWipChangesParams // ------------- Required query parameter "refName" ------------- @@ -6209,8 +6201,16 @@ func (siw *ServerInterfaceWrapper) CreateWip(w http.ResponseWriter, r *http.Requ return } + // ------------- Optional query parameter "path" ------------- + + err = runtime.BindQueryParameter("form", true, false, "path", r.URL.Query(), ¶ms.Path) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CreateWip(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + siw.Handler.GetWipChanges(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -6220,8 +6220,8 @@ func (siw *ServerInterfaceWrapper) CreateWip(w http.ResponseWriter, r *http.Requ handler.ServeHTTP(w, r.WithContext(ctx)) } -// GetWipChanges operation middleware -func (siw *ServerInterfaceWrapper) GetWipChanges(w http.ResponseWriter, r *http.Request) { +// CommitWip operation middleware +func (siw *ServerInterfaceWrapper) CommitWip(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -6251,33 +6251,40 @@ func (siw *ServerInterfaceWrapper) GetWipChanges(w http.ResponseWriter, r *http. ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context - var params GetWipChangesParams + var params CommitWipParams - // ------------- Required query parameter "refName" ------------- + // ------------- Required query parameter "msg" ------------- - if paramValue := r.URL.Query().Get("refName"); paramValue != "" { + if paramValue := r.URL.Query().Get("msg"); paramValue != "" { } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "msg"}) return } - err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) + err = runtime.BindQueryParameter("form", true, true, "msg", r.URL.Query(), ¶ms.Msg) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "msg", Err: err}) return } - // ------------- Optional query parameter "path" ------------- + // ------------- Required query parameter "refName" ------------- - err = runtime.BindQueryParameter("form", true, false, "path", r.URL.Query(), ¶ms.Path) + if paramValue := r.URL.Query().Get("refName"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) return } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetWipChanges(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + siw.Handler.CommitWip(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -6287,8 +6294,8 @@ func (siw *ServerInterfaceWrapper) GetWipChanges(w http.ResponseWriter, r *http. handler.ServeHTTP(w, r.WithContext(ctx)) } -// CommitWip operation middleware -func (siw *ServerInterfaceWrapper) CommitWip(w http.ResponseWriter, r *http.Request) { +// ListWip operation middleware +func (siw *ServerInterfaceWrapper) ListWip(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -6317,41 +6324,8 @@ func (siw *ServerInterfaceWrapper) CommitWip(w http.ResponseWriter, r *http.Requ ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - // Parameter object where we will unmarshal all parameters from the context - var params CommitWipParams - - // ------------- Required query parameter "msg" ------------- - - if paramValue := r.URL.Query().Get("msg"); paramValue != "" { - - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "msg"}) - return - } - - err = runtime.BindQueryParameter("form", true, true, "msg", r.URL.Query(), ¶ms.Msg) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "msg", Err: err}) - return - } - - // ------------- Required query parameter "refName" ------------- - - if paramValue := r.URL.Query().Get("refName"); paramValue != "" { - - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) - return - } - - err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) - return - } - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CommitWip(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + siw.Handler.ListWip(r.Context(), &JiaozifsResponse{w}, r, owner, repository) })) for _, middleware := range siw.HandlerMiddlewares { @@ -6361,8 +6335,8 @@ func (siw *ServerInterfaceWrapper) CommitWip(w http.ResponseWriter, r *http.Requ handler.ServeHTTP(w, r.WithContext(ctx)) } -// ListWip operation middleware -func (siw *ServerInterfaceWrapper) ListWip(w http.ResponseWriter, r *http.Request) { +// RevertWip operation middleware +func (siw *ServerInterfaceWrapper) RevertWip(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -6391,8 +6365,26 @@ func (siw *ServerInterfaceWrapper) ListWip(w http.ResponseWriter, r *http.Reques ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context + var params RevertWipParams + + // ------------- Required query parameter "refName" ------------- + + if paramValue := r.URL.Query().Get("refName"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.ListWip(r.Context(), &JiaozifsResponse{w}, r, owner, repository) + siw.Handler.RevertWip(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -6595,9 +6587,6 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/wip/{owner}/{repository}", wrapper.GetWip) }) - r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/wip/{owner}/{repository}", wrapper.CreateWip) - }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/wip/{owner}/{repository}/changes", wrapper.GetWipChanges) }) @@ -6607,6 +6596,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/wip/{owner}/{repository}/list", wrapper.ListWip) }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/wip/{owner}/{repository}/revert", wrapper.RevertWip) + }) return r } @@ -6614,75 +6606,75 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "AAA=", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index eef2b0bb..54704a57 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -357,6 +357,31 @@ components: type: string is_dir: type: boolean + FullTreeEntry: + type: object + required: + - name + - hash + - is_dir + - size + - created_at + - updated_at + properties: + name: + type: string + hash: + type: string + is_dir: + type: boolean + size: + type: integer + format: int64 + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time TreeNode: type: object required: @@ -1007,26 +1032,39 @@ paths: description: Unauthorized 403: description: Forbidden + + /wip/{owner}/{repository}/revert: + parameters: + - in: path + name: repository + required: true + schema: + type: string + - in: path + name: owner + required: true + schema: + type: string + - in: query + name: refName + description: ref name + required: true + schema: + type: string post: tags: - wip - operationId: createWip - summary: create working in process + operationId: revertWip + summary: revert working in process responses: - 201: - description: working in process created - content: - application/json: - schema: - $ref: "#/components/schemas/Wip" + 200: + description: success to revert wip 400: description: ValidationError 401: description: Unauthorized 403: description: Forbidden - 502: - description: internal server error /wip/{owner}/{repository}/changes: parameters: @@ -1197,7 +1235,7 @@ paths: schema: type: array items: - $ref: "#/components/schemas/TreeEntry" + $ref: "#/components/schemas/FullTreeEntry" 400: description: ValidationError 401: diff --git a/controller/wip_ctl.go b/controller/wip_ctl.go index b105f339..aae91e4d 100644 --- a/controller/wip_ctl.go +++ b/controller/wip_ctl.go @@ -26,75 +26,6 @@ type WipController struct { PublicStorageConfig params.AdapterConfig } -// CreateWip create wip of branch -func (wipCtl WipController) CreateWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.CreateWipParams) { - operator, err := auth.GetOperator(ctx) - if err != nil { - w.Error(err) - return - } - - owner, err := wipCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) - if err != nil { - w.Error(err) - return - } - - repository, err := wipCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) - if err != nil { - w.Error(err) - return - } - - if operator.Name != owner.Name { //todo check permission to operator ownerRepo - w.Forbidden() - return - } - - ref, err := wipCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(params.RefName)) - if err != nil { - w.Error(err) - return - } - - wip, err := wipCtl.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetCreatorID(operator.ID).SetRepositoryID(repository.ID).SetRefID(ref.ID)) - if err == nil { - w.JSON(wip) - return - } - if err != nil && !errors.Is(err, models.ErrNotFound) { - w.Error(err) - return - } - - currentTreeHash := hash.EmptyHash - if !ref.CommitHash.IsEmpty() { - baseCommit, err := wipCtl.Repo.CommitRepo(repository.ID).Commit(ctx, ref.CommitHash) - if err != nil { - w.Error(err) - return - } - currentTreeHash = baseCommit.TreeHash - } - - wip = &models.WorkingInProcess{ - CurrentTree: currentTreeHash, - BaseCommit: ref.CommitHash, - RepositoryID: repository.ID, - RefID: ref.ID, - State: 0, - CreatorID: operator.ID, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - wip, err = wipCtl.Repo.WipRepo().Insert(ctx, wip) - if err != nil { - w.Error(err) - return - } - w.JSON(wip, http.StatusCreated) -} - // GetWip get wip of specific repository, operator only get himself wip func (wipCtl WipController) GetWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.GetWipParams) { operator, err := auth.GetOperator(ctx) @@ -391,3 +322,49 @@ func (wipCtl WipController) GetWipChanges(ctx context.Context, w *api.JiaozifsRe w.JSON(changesResp) } + +// RevertWip +func (wipCtl WipController) RevertWip(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, ownerName string, repositoryName string, params api.RevertWipParams) { + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return + } + + owner, err := wipCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + repository, err := wipCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) + if err != nil { + w.Error(err) + return + } + + if operator.Name != owner.Name { //todo check permission to operator ownerRepo + w.Forbidden() + return + } + + workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repository, wipCtl.Repo, wipCtl.PublicStorageConfig) + if err != nil { + w.Error(err) + return + } + + err = workRepo.CheckOut(ctx, versionmgr.InWip, params.RefName) + if err != nil { + w.Error(err) + return + } + + err = workRepo.RevertWip(ctx) + if err != nil { + w.Error(err) + return + } + + w.OK() +} diff --git a/models/wip.go b/models/wip.go index 9058705b..fa551a95 100644 --- a/models/wip.go +++ b/models/wip.go @@ -124,8 +124,8 @@ type UpdateWipParams struct { UpdatedAt time.Time `bun:"updated_at"` } -func NewUpdateWipParams(ID uuid.UUID) *UpdateWipParams { - return &UpdateWipParams{ID: ID, UpdatedAt: time.Now()} +func NewUpdateWipParams(id uuid.UUID) *UpdateWipParams { + return &UpdateWipParams{ID: id, UpdatedAt: time.Now()} } func (up *UpdateWipParams) SetCurrentTree(currentTree hash.Hash) *UpdateWipParams { diff --git a/models/wip_test.go b/models/wip_test.go index f8dd8640..49ff6cd8 100644 --- a/models/wip_test.go +++ b/models/wip_test.go @@ -77,31 +77,43 @@ func TestWipRepoUpdateByID(t *testing.T) { repo := models.NewWipRepo(db) - wipModel := &models.WorkingInProcess{} - require.NoError(t, gofakeit.Struct(wipModel)) - newWipModel, err := repo.Insert(ctx, wipModel) - require.NoError(t, err) - require.NotEqual(t, uuid.Nil, newWipModel.ID) + t.Run("only update tree hash", func(t *testing.T) { + wipModel := &models.WorkingInProcess{} + require.NoError(t, gofakeit.Struct(wipModel)) + newWipModel, err := repo.Insert(ctx, wipModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newWipModel.ID) - getWipParams := models.NewGetWipParams(). - SetID(newWipModel.ID). - SetCreatorID(newWipModel.CreatorID). - SetRepositoryID(newWipModel.RepositoryID). - SetRefID(newWipModel.RefID) - user, err := repo.Get(ctx, getWipParams) - require.NoError(t, err) - require.True(t, cmp.Equal(newWipModel, user, dbTimeCmpOpt)) + updateModel := models.NewUpdateWipParams(newWipModel.ID). + SetCurrentTree(hash.Hash("mock hash")) - updateModel := models.NewUpdateWipParams(newWipModel.ID). - SetState(models.Completed). - SetBaseCommit(hash.Hash("mock base hash")). - SetCurrentTree(hash.Hash("mock hash")) + err = repo.UpdateByID(ctx, updateModel) + require.NoError(t, err) + updatedUser, err := repo.Get(ctx, models.NewGetWipParams().SetID(newWipModel.ID)) + require.NoError(t, err) + require.Equal(t, newWipModel.State, updatedUser.State) + require.Equal(t, newWipModel.BaseCommit, updatedUser.BaseCommit) + require.Equal(t, "mock hash", string(updatedUser.CurrentTree)) + }) - err = repo.UpdateByID(ctx, updateModel) - require.NoError(t, err) - updatedUser, err := repo.Get(ctx, models.NewGetWipParams().SetID(newWipModel.ID)) - require.NoError(t, err) - require.Equal(t, models.Completed, updatedUser.State) - require.Equal(t, "mock base hash", string(updatedUser.BaseCommit)) - require.Equal(t, "mock hash", string(updatedUser.CurrentTree)) + t.Run("update both", func(t *testing.T) { + wipModel := &models.WorkingInProcess{} + require.NoError(t, gofakeit.Struct(wipModel)) + newWipModel, err := repo.Insert(ctx, wipModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newWipModel.ID) + + updateModel := models.NewUpdateWipParams(newWipModel.ID). + SetState(models.Completed). + SetBaseCommit(hash.Hash("mock base hash")). + SetCurrentTree(hash.Hash("mock hash")) + + err = repo.UpdateByID(ctx, updateModel) + require.NoError(t, err) + updatedUser, err := repo.Get(ctx, models.NewGetWipParams().SetID(newWipModel.ID)) + require.NoError(t, err) + require.Equal(t, models.Completed, updatedUser.State) + require.Equal(t, "mock base hash", string(updatedUser.BaseCommit)) + require.Equal(t, "mock hash", string(updatedUser.CurrentTree)) + }) } diff --git a/versionmgr/work_repo.go b/versionmgr/work_repo.go index 34a6d25a..bc3876d5 100644 --- a/versionmgr/work_repo.go +++ b/versionmgr/work_repo.go @@ -207,6 +207,26 @@ func (repository *WorkRepository) CheckOut(ctx context.Context, refType WorkRepo return nil } +func (repository *WorkRepository) RevertWip(ctx context.Context) error { + if repository.state != InWip { + return fmt.Errorf("working repo not in wip state") + } + treeHash := hash.EmptyHash + if !repository.wip.BaseCommit.IsEmpty() { + commit, err := repository.repo.CommitRepo(repository.repoModel.ID).Commit(ctx, repository.wip.BaseCommit) + if err != nil { + return err + } + treeHash = commit.TreeHash + } + err := repository.repo.WipRepo().UpdateByID(ctx, models.NewUpdateWipParams(repository.wip.ID).SetCurrentTree(treeHash)) + if err != nil { + return err + } + repository.wip.CurrentTree = treeHash + return nil +} + // CommitChanges append a new commit to current headTree, read changes from wip, than create a new commit with parent point to current headTree, // and replace tree hash with wip's currentTreeHash. func (repository *WorkRepository) CommitChanges(ctx context.Context, msg string) (*models.Commit, error) { diff --git a/versionmgr/worktree.go b/versionmgr/worktree.go index 429a551e..501c9153 100644 --- a/versionmgr/worktree.go +++ b/versionmgr/worktree.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" "strings" + "time" "github.com/google/uuid" "github.com/jiaozifs/jiaozifs/models" @@ -27,6 +28,16 @@ var EmptyDirEntry = models.TreeEntry{ Hash: hash.Hash([]byte{}), } +type FullTreeEntry struct { + Name string `json:"name"` + IsDir bool `json:"is_dir"` + Hash hash.Hash `json:"hash"` + Size int64 `json:"size"` + + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + var ( ErrPathNotFound = fmt.Errorf("path not found") ErrEntryExit = fmt.Errorf("entry exit") @@ -409,10 +420,10 @@ func (workTree *WorkTree) RemoveEntry(ctx context.Context, fullPath string) erro // // Ls(ctx, root, "a") return b // Ls(ctx, root, "a/b" return c.txt and d.txt -func (workTree *WorkTree) Ls(ctx context.Context, fullPath string) ([]models.TreeEntry, error) { +func (workTree *WorkTree) Ls(ctx context.Context, fullPath string) ([]FullTreeEntry, error) { fullPath = CleanPath(fullPath) if len(fullPath) == 0 { - return workTree.root.SubObjects(), nil + return workTree.getFullEntry(ctx, workTree.root.SubObjects()) } existNode, missingPath, err := workTree.matchPath(ctx, fullPath) @@ -428,8 +439,37 @@ func (workTree *WorkTree) Ls(ctx context.Context, fullPath string) ([]models.Tre if lastNode.Node().Type != models.TreeObject { return nil, ErrNotDirectory } + return workTree.getFullEntry(ctx, lastNode.Node().SubObjects) +} - return lastNode.Node().SubObjects, nil +func (workTree *WorkTree) getFullEntry(ctx context.Context, treeEntries []models.TreeEntry) ([]FullTreeEntry, error) { + entries := make([]FullTreeEntry, 0) + for _, entry := range treeEntries { + fe := FullTreeEntry{ + Name: entry.Name, + IsDir: entry.IsDir, + Hash: entry.Hash, + } + if entry.IsDir { + blob, err := workTree.object.TreeNode(ctx, entry.Hash) + if err != nil { + return nil, err + } + fe.CreatedAt = blob.CreatedAt + fe.UpdatedAt = blob.UpdatedAt + entries = append(entries, fe) + } else { + blob, err := workTree.object.Blob(ctx, entry.Hash) + if err != nil { + return nil, err + } + fe.Size = blob.Size + fe.CreatedAt = blob.CreatedAt + fe.UpdatedAt = blob.UpdatedAt + entries = append(entries, fe) + } + } + return entries, nil } func (workTree *WorkTree) FindBlob(ctx context.Context, fullPath string) (*models.Blob, string, error) { From 35112af380804a93acce2d5921cbb8e0eae4ea1e Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Fri, 29 Dec 2023 15:56:18 +0800 Subject: [PATCH 131/210] fix windwos path split --- versionmgr/worktree.go | 12 ++++++------ versionmgr/worktree_test.go | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/versionmgr/worktree.go b/versionmgr/worktree.go index 501c9153..8e61e3ee 100644 --- a/versionmgr/worktree.go +++ b/versionmgr/worktree.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "os" "path/filepath" "strings" "time" @@ -159,7 +158,7 @@ func (workTree *WorkTree) ReplaceSubTreeEntry(ctx context.Context, treeEntry mod } func (workTree *WorkTree) matchPath(ctx context.Context, path string) ([]FullObject, []string, error) { - pathSegs := strings.Split(path, fmt.Sprintf("%c", os.PathSeparator)) + pathSegs := strings.Split(path, "/") //path must be unix style var existNodes []FullObject var missingPath []string //a/b/c/d/e @@ -532,9 +531,10 @@ func (workTree *WorkTree) Diff(ctx context.Context, rootTreeHash hash.Hash) (*Ch } // CleanPath clean path -// 1. trim space -// 2. trim first or last / -// 3. to slash +// 1. replace \\ to / +// 2. trim space +// 3. trim first or last / func CleanPath(fullPath string) string { - return filepath.ToSlash(strings.Trim(strings.TrimSpace(fullPath), "/")) + path := strings.ReplaceAll(fullPath, "\\", "/") + return filepath.ToSlash(strings.Trim(strings.TrimSpace(path), "/\\")) } diff --git a/versionmgr/worktree_test.go b/versionmgr/worktree_test.go index 33d51456..1d8a6fe5 100644 --- a/versionmgr/worktree_test.go +++ b/versionmgr/worktree_test.go @@ -144,4 +144,5 @@ func TestCleanPath(t *testing.T) { require.Equal(t, "a/b/c", CleanPath("a/b/c")) require.Equal(t, "a/b/c", CleanPath("/a/b/c/")) + require.Equal(t, "a/b/c", CleanPath("\\a\\b\\c\\")) } From 33e21ca6f31eddbc8ddf8fc468966cb2f73ea414 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Fri, 29 Dec 2023 16:50:48 +0800 Subject: [PATCH 132/210] feat: add api to get commit changes --- README.md | 3 - api/jiaozifs.gen.go | 417 +++++++++++++++++++++++------- api/swagger.yml | 48 +++- controller/branch_ctl.go | 57 ++-- controller/commit_ctl.go | 74 +++++- controller/wip_ctl.go | 60 ++--- integrationtest/commit_test.go | 117 ++++++++- integrationtest/root_test.go | 2 + integrationtest/wip_test.go | 2 +- models/models.go | 4 +- utils/hash/hash.go | 14 +- utils/hash/hash_test.go | 14 + versionmgr/work_repo.go | 134 +++++++++- versionmgr/work_repo_diff_test.go | 10 +- 14 files changed, 758 insertions(+), 198 deletions(-) diff --git a/README.md b/README.md index b091d319..e54919c8 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,3 @@ init and running ./jzfs daemon ``` - - -revert object只revert一个对象 \ No newline at end of file diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 5b43ffba..6a4d1d8a 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -383,6 +383,12 @@ type ListBranchesParams struct { Amount *PaginationAmount `form:"amount,omitempty" json:"amount,omitempty"` } +// GetCommitChangesParams defines parameters for GetCommitChanges. +type GetCommitChangesParams struct { + // Path specific path, if not specific return entries in root + Path *string `form:"path,omitempty" json:"path,omitempty"` +} + // GetCommitsInRepositoryParams defines parameters for GetCommitsInRepository. type GetCommitsInRepositoryParams struct { // After return items after this value @@ -395,8 +401,8 @@ type GetCommitsInRepositoryParams struct { RefName *string `form:"refName,omitempty" json:"refName,omitempty"` } -// GetCommitDiffParams defines parameters for GetCommitDiff. -type GetCommitDiffParams struct { +// CompareCommitParams defines parameters for CompareCommit. +type CompareCommitParams struct { // Path specific path, if not specific return entries in root Path *string `form:"path,omitempty" json:"path,omitempty"` } @@ -609,11 +615,14 @@ type ClientInterface interface { // ListBranches request ListBranches(ctx context.Context, owner string, repository string, params *ListBranchesParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetCommitChanges request + GetCommitChanges(ctx context.Context, owner string, repository string, commitId string, params *GetCommitChangesParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetCommitsInRepository request GetCommitsInRepository(ctx context.Context, owner string, repository string, params *GetCommitsInRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) - // GetCommitDiff request - GetCommitDiff(ctx context.Context, owner string, repository string, basehead string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // CompareCommit request + CompareCommit(ctx context.Context, owner string, repository string, basehead string, params *CompareCommitParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetEntriesInRef request GetEntriesInRef(ctx context.Context, owner string, repository string, params *GetEntriesInRefParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -854,6 +863,18 @@ func (c *Client) ListBranches(ctx context.Context, owner string, repository stri return c.Client.Do(req) } +func (c *Client) GetCommitChanges(ctx context.Context, owner string, repository string, commitId string, params *GetCommitChangesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetCommitChangesRequest(c.Server, owner, repository, commitId, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) GetCommitsInRepository(ctx context.Context, owner string, repository string, params *GetCommitsInRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewGetCommitsInRepositoryRequest(c.Server, owner, repository, params) if err != nil { @@ -866,8 +887,8 @@ func (c *Client) GetCommitsInRepository(ctx context.Context, owner string, repos return c.Client.Do(req) } -func (c *Client) GetCommitDiff(ctx context.Context, owner string, repository string, basehead string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetCommitDiffRequest(c.Server, owner, repository, basehead, params) +func (c *Client) CompareCommit(ctx context.Context, owner string, repository string, basehead string, params *CompareCommitParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCompareCommitRequest(c.Server, owner, repository, basehead, params) if err != nil { return nil, err } @@ -1880,6 +1901,76 @@ func NewListBranchesRequest(server string, owner string, repository string, para return req, nil } +// NewGetCommitChangesRequest generates requests for GetCommitChanges +func NewGetCommitChangesRequest(server string, owner string, repository string, commitId string, params *GetCommitChangesParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "commit_id", runtime.ParamLocationPath, commitId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/repos/%s/%s/changes/%s", pathParam0, pathParam1, pathParam2) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Path != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewGetCommitsInRepositoryRequest generates requests for GetCommitsInRepository func NewGetCommitsInRepositoryRequest(server string, owner string, repository string, params *GetCommitsInRepositoryParams) (*http.Request, error) { var err error @@ -1975,8 +2066,8 @@ func NewGetCommitsInRepositoryRequest(server string, owner string, repository st return req, nil } -// NewGetCommitDiffRequest generates requests for GetCommitDiff -func NewGetCommitDiffRequest(server string, owner string, repository string, basehead string, params *GetCommitDiffParams) (*http.Request, error) { +// NewCompareCommitRequest generates requests for CompareCommit +func NewCompareCommitRequest(server string, owner string, repository string, basehead string, params *CompareCommitParams) (*http.Request, error) { var err error var pathParam0 string @@ -2918,11 +3009,14 @@ type ClientWithResponsesInterface interface { // ListBranchesWithResponse request ListBranchesWithResponse(ctx context.Context, owner string, repository string, params *ListBranchesParams, reqEditors ...RequestEditorFn) (*ListBranchesResponse, error) + // GetCommitChangesWithResponse request + GetCommitChangesWithResponse(ctx context.Context, owner string, repository string, commitId string, params *GetCommitChangesParams, reqEditors ...RequestEditorFn) (*GetCommitChangesResponse, error) + // GetCommitsInRepositoryWithResponse request GetCommitsInRepositoryWithResponse(ctx context.Context, owner string, repository string, params *GetCommitsInRepositoryParams, reqEditors ...RequestEditorFn) (*GetCommitsInRepositoryResponse, error) - // GetCommitDiffWithResponse request - GetCommitDiffWithResponse(ctx context.Context, owner string, repository string, basehead string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*GetCommitDiffResponse, error) + // CompareCommitWithResponse request + CompareCommitWithResponse(ctx context.Context, owner string, repository string, basehead string, params *CompareCommitParams, reqEditors ...RequestEditorFn) (*CompareCommitResponse, error) // GetEntriesInRefWithResponse request GetEntriesInRefWithResponse(ctx context.Context, owner string, repository string, params *GetEntriesInRefParams, reqEditors ...RequestEditorFn) (*GetEntriesInRefResponse, error) @@ -3250,6 +3344,28 @@ func (r ListBranchesResponse) StatusCode() int { return 0 } +type GetCommitChangesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]Change +} + +// Status returns HTTPResponse.Status +func (r GetCommitChangesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetCommitChangesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type GetCommitsInRepositoryResponse struct { Body []byte HTTPResponse *http.Response @@ -3272,14 +3388,14 @@ func (r GetCommitsInRepositoryResponse) StatusCode() int { return 0 } -type GetCommitDiffResponse struct { +type CompareCommitResponse struct { Body []byte HTTPResponse *http.Response JSON200 *[]Change } // Status returns HTTPResponse.Status -func (r GetCommitDiffResponse) Status() string { +func (r CompareCommitResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3287,7 +3403,7 @@ func (r GetCommitDiffResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetCommitDiffResponse) StatusCode() int { +func (r CompareCommitResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -3740,6 +3856,15 @@ func (c *ClientWithResponses) ListBranchesWithResponse(ctx context.Context, owne return ParseListBranchesResponse(rsp) } +// GetCommitChangesWithResponse request returning *GetCommitChangesResponse +func (c *ClientWithResponses) GetCommitChangesWithResponse(ctx context.Context, owner string, repository string, commitId string, params *GetCommitChangesParams, reqEditors ...RequestEditorFn) (*GetCommitChangesResponse, error) { + rsp, err := c.GetCommitChanges(ctx, owner, repository, commitId, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetCommitChangesResponse(rsp) +} + // GetCommitsInRepositoryWithResponse request returning *GetCommitsInRepositoryResponse func (c *ClientWithResponses) GetCommitsInRepositoryWithResponse(ctx context.Context, owner string, repository string, params *GetCommitsInRepositoryParams, reqEditors ...RequestEditorFn) (*GetCommitsInRepositoryResponse, error) { rsp, err := c.GetCommitsInRepository(ctx, owner, repository, params, reqEditors...) @@ -3749,13 +3874,13 @@ func (c *ClientWithResponses) GetCommitsInRepositoryWithResponse(ctx context.Con return ParseGetCommitsInRepositoryResponse(rsp) } -// GetCommitDiffWithResponse request returning *GetCommitDiffResponse -func (c *ClientWithResponses) GetCommitDiffWithResponse(ctx context.Context, owner string, repository string, basehead string, params *GetCommitDiffParams, reqEditors ...RequestEditorFn) (*GetCommitDiffResponse, error) { - rsp, err := c.GetCommitDiff(ctx, owner, repository, basehead, params, reqEditors...) +// CompareCommitWithResponse request returning *CompareCommitResponse +func (c *ClientWithResponses) CompareCommitWithResponse(ctx context.Context, owner string, repository string, basehead string, params *CompareCommitParams, reqEditors ...RequestEditorFn) (*CompareCommitResponse, error) { + rsp, err := c.CompareCommit(ctx, owner, repository, basehead, params, reqEditors...) if err != nil { return nil, err } - return ParseGetCommitDiffResponse(rsp) + return ParseCompareCommitResponse(rsp) } // GetEntriesInRefWithResponse request returning *GetEntriesInRefResponse @@ -4168,6 +4293,32 @@ func ParseListBranchesResponse(rsp *http.Response) (*ListBranchesResponse, error return response, nil } +// ParseGetCommitChangesResponse parses an HTTP response from a GetCommitChangesWithResponse call +func ParseGetCommitChangesResponse(rsp *http.Response) (*GetCommitChangesResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetCommitChangesResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []Change + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseGetCommitsInRepositoryResponse parses an HTTP response from a GetCommitsInRepositoryWithResponse call func ParseGetCommitsInRepositoryResponse(rsp *http.Response) (*GetCommitsInRepositoryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -4194,15 +4345,15 @@ func ParseGetCommitsInRepositoryResponse(rsp *http.Response) (*GetCommitsInRepos return response, nil } -// ParseGetCommitDiffResponse parses an HTTP response from a GetCommitDiffWithResponse call -func ParseGetCommitDiffResponse(rsp *http.Response) (*GetCommitDiffResponse, error) { +// ParseCompareCommitResponse parses an HTTP response from a CompareCommitWithResponse call +func ParseCompareCommitResponse(rsp *http.Response) (*CompareCommitResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetCommitDiffResponse{ + response := &CompareCommitResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -4595,12 +4746,15 @@ type ServerInterface interface { // list branches // (GET /repos/{owner}/{repository}/branches) ListBranches(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params ListBranchesParams) + // get changes in commit + // (GET /repos/{owner}/{repository}/changes/{commit_id}) + GetCommitChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, commitId string, params GetCommitChangesParams) // get commits in repository // (GET /repos/{owner}/{repository}/commits) GetCommitsInRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetCommitsInRepositoryParams) - // get commit differences + // compare two commit // (GET /repos/{owner}/{repository}/compare/{basehead}) - GetCommitDiff(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, basehead string, params GetCommitDiffParams) + CompareCommit(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, basehead string, params CompareCommitParams) // list entries in ref // (GET /repos/{owner}/{repository}/contents) GetEntriesInRef(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetEntriesInRefParams) @@ -4726,15 +4880,21 @@ func (_ Unimplemented) ListBranches(ctx context.Context, w *JiaozifsResponse, r w.WriteHeader(http.StatusNotImplemented) } +// get changes in commit +// (GET /repos/{owner}/{repository}/changes/{commit_id}) +func (_ Unimplemented) GetCommitChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, commitId string, params GetCommitChangesParams) { + w.WriteHeader(http.StatusNotImplemented) +} + // get commits in repository // (GET /repos/{owner}/{repository}/commits) func (_ Unimplemented) GetCommitsInRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetCommitsInRepositoryParams) { w.WriteHeader(http.StatusNotImplemented) } -// get commit differences +// compare two commit // (GET /repos/{owner}/{repository}/compare/{basehead}) -func (_ Unimplemented) GetCommitDiff(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, basehead string, params GetCommitDiffParams) { +func (_ Unimplemented) CompareCommit(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, basehead string, params CompareCommitParams) { w.WriteHeader(http.StatusNotImplemented) } @@ -5615,6 +5775,67 @@ func (siw *ServerInterfaceWrapper) ListBranches(w http.ResponseWriter, r *http.R handler.ServeHTTP(w, r.WithContext(ctx)) } +// GetCommitChanges operation middleware +func (siw *ServerInterfaceWrapper) GetCommitChanges(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + // ------------- Path parameter "commit_id" ------------- + var commitId string + + err = runtime.BindStyledParameterWithOptions("simple", "commit_id", chi.URLParam(r, "commit_id"), &commitId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "commit_id", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetCommitChangesParams + + // ------------- Optional query parameter "path" ------------- + + err = runtime.BindQueryParameter("form", true, false, "path", r.URL.Query(), ¶ms.Path) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetCommitChanges(r.Context(), &JiaozifsResponse{w}, r, owner, repository, commitId, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // GetCommitsInRepository operation middleware func (siw *ServerInterfaceWrapper) GetCommitsInRepository(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -5683,8 +5904,8 @@ func (siw *ServerInterfaceWrapper) GetCommitsInRepository(w http.ResponseWriter, handler.ServeHTTP(w, r.WithContext(ctx)) } -// GetCommitDiff operation middleware -func (siw *ServerInterfaceWrapper) GetCommitDiff(w http.ResponseWriter, r *http.Request) { +// CompareCommit operation middleware +func (siw *ServerInterfaceWrapper) CompareCommit(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -5723,7 +5944,7 @@ func (siw *ServerInterfaceWrapper) GetCommitDiff(w http.ResponseWriter, r *http. ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context - var params GetCommitDiffParams + var params CompareCommitParams // ------------- Optional query parameter "path" ------------- @@ -5734,7 +5955,7 @@ func (siw *ServerInterfaceWrapper) GetCommitDiff(w http.ResponseWriter, r *http. } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetCommitDiff(r.Context(), &JiaozifsResponse{w}, r, owner, repository, basehead, params) + siw.Handler.CompareCommit(r.Context(), &JiaozifsResponse{w}, r, owner, repository, basehead, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -6551,11 +6772,14 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/repos/{owner}/{repository}/branches", wrapper.ListBranches) }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/repos/{owner}/{repository}/changes/{commit_id}", wrapper.GetCommitChanges) + }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/repos/{owner}/{repository}/commits", wrapper.GetCommitsInRepository) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/repos/{owner}/{repository}/compare/{basehead}", wrapper.GetCommitDiff) + r.Get(options.BaseURL+"/repos/{owner}/{repository}/compare/{basehead}", wrapper.CompareCommit) }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/repos/{owner}/{repository}/contents", wrapper.GetEntriesInRef) @@ -6606,75 +6830,76 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "///hLh6kxGsAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 54704a57..b4e7f3e2 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -1265,8 +1265,8 @@ paths: get: tags: - commit - operationId: getCommitDiff - summary: get commit differences + operationId: compareCommit + summary: compare two commit parameters: - in: query name: path @@ -1290,6 +1290,50 @@ paths: 503: description: server internal error + /repos/{owner}/{repository}/changes/{commit_id}: + parameters: + - in: path + name: owner + required: true + schema: + type: string + - in: path + name: repository + required: true + schema: + type: string + - in: path + name: commit_id + required: true + schema: + type: string + get: + tags: + - commit + operationId: getCommitChanges + summary: get changes in commit + parameters: + - in: query + name: path + description: specific path, if not specific return entries in root + required: false + schema: + type: string + responses: + 200: + description: commit diff + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Change" + 400: + description: ValidationError + 401: + description: Unauthorized + 503: + description: server internal error /repos/{owner}/{repository}/commits: parameters: - in: path diff --git a/controller/branch_ctl.go b/controller/branch_ctl.go index d3f1077c..8cbcf177 100644 --- a/controller/branch_ctl.go +++ b/controller/branch_ctl.go @@ -7,13 +7,15 @@ import ( "net/http" "regexp" "strings" - "time" + + "github.com/jiaozifs/jiaozifs/block/params" + + "github.com/jiaozifs/jiaozifs/versionmgr" "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/auth" "github.com/jiaozifs/jiaozifs/models" "github.com/jiaozifs/jiaozifs/utils" - "github.com/jiaozifs/jiaozifs/utils/hash" "go.uber.org/fx" ) @@ -50,7 +52,8 @@ func CheckBranchName(name string) error { type BranchController struct { fx.In - Repo models.IRepo + Repo models.IRepo + PublicStorageConfig params.AdapterConfig } func (bct BranchController) ListBranches(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.ListBranchesParams) { @@ -153,16 +156,6 @@ func (bct BranchController) CreateBranch(ctx context.Context, w *api.JiaozifsRes return } - //check exit - _, err = bct.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetName(body.Name).SetRepositoryID(repository.ID)) - if err == nil { - w.BadRequest(fmt.Sprintf("%s already exit", body.Name)) - return - } - if err != nil && !errors.Is(err, models.ErrNotFound) { - w.Error(err) - return - } //get source branch sourceBranch, err := bct.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetName(body.Source).SetRepositoryID(repository.ID)) if err != nil && !errors.Is(err, models.ErrNotFound) { @@ -170,25 +163,24 @@ func (bct BranchController) CreateBranch(ctx context.Context, w *api.JiaozifsRes return } - commitHash := hash.EmptyHash - if sourceBranch != nil { - commitHash = sourceBranch.CommitHash + workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repository, bct.Repo, bct.PublicStorageConfig) + if err != nil { + w.Error(err) + return } - // Create branch - newBranch := &models.Branch{ - RepositoryID: repository.ID, - CommitHash: commitHash, - Name: body.Name, - CreatorID: operator.ID, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), + err = workRepo.CheckOut(ctx, versionmgr.InCommit, sourceBranch.CommitHash.Hex()) + if err != nil { + w.Error(err) + return } - newBranch, err = bct.Repo.BranchRepo().Insert(ctx, newBranch) + + newBranch, err := workRepo.CreateBranch(ctx, body.Name) if err != nil { w.Error(err) return } + w.JSON(api.Branch{ CommitHash: newBranch.CommitHash.Hex(), CreatedAt: newBranch.CreatedAt, @@ -226,14 +218,21 @@ func (bct BranchController) DeleteBranch(ctx context.Context, w *api.JiaozifsRes return } - // Delete branch - affectedRows, err := bct.Repo.BranchRepo().Delete(ctx, models.NewDeleteBranchParams().SetName(params.RefName).SetRepositoryID(repository.ID)) + workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repository, bct.Repo, bct.PublicStorageConfig) + if err != nil { + w.Error(err) + return + } + + err = workRepo.CheckOut(ctx, versionmgr.InBranch, params.RefName) if err != nil { w.Error(err) return } - if affectedRows == 0 { - w.NotFound() + + err = workRepo.DeleteBranch(ctx) + if err != nil { + w.Error(err) return } w.OK() diff --git a/controller/commit_ctl.go b/controller/commit_ctl.go index f325f160..e38f8a21 100644 --- a/controller/commit_ctl.go +++ b/controller/commit_ctl.go @@ -106,7 +106,7 @@ func (commitCtl CommitController) GetEntriesInRef(ctx context.Context, w *api.Ji w.JSON(treeEntry) } -func (commitCtl CommitController) GetCommitDiff(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, basehead string, params api.GetCommitDiffParams) { +func (commitCtl CommitController) CompareCommit(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, basehead string, params api.CompareCommitParams) { operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) @@ -189,3 +189,75 @@ func (commitCtl CommitController) GetCommitDiff(ctx context.Context, w *api.Jiao } w.JSON(changesResp) } + +func (commitCtl CommitController) GetCommitChanges(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, commitID string, params api.GetCommitChangesParams) { + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return + } + + owner, err := commitCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + repository, err := commitCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) + if err != nil { + w.Error(err) + return + } + + if operator.ID != owner.ID { //todo check permission + w.Forbidden() + return + } + + workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repository, commitCtl.Repo, commitCtl.PublicStorageConfig) + if err != nil { + w.Error(err) + return + } + + err = workRepo.CheckOut(ctx, versionmgr.InCommit, commitID) + if err != nil { + w.Error(err) + return + } + + changes, err := workRepo.GetCommitChanges(ctx) + if err != nil { + w.Error(err) + return + } + + path := versionmgr.CleanPath(utils.StringValue(params.Path)) + var changesResp []api.Change + err = changes.ForEach(func(change versionmgr.IChange) error { + action, err := change.Action() + if err != nil { + return err + } + fullPath := change.Path() + if strings.HasPrefix(fullPath, path) { + apiChange := api.Change{ + Action: api.ChangeAction(action), + Path: fullPath, + } + if change.From() != nil { + apiChange.BaseHash = utils.String(hex.EncodeToString(change.From().Hash())) + } + if change.To() != nil { + apiChange.ToHash = utils.String(hex.EncodeToString(change.To().Hash())) + } + changesResp = append(changesResp, apiChange) + } + return nil + }) + if err != nil { + w.Error(err) + return + } + w.JSON(changesResp) +} diff --git a/controller/wip_ctl.go b/controller/wip_ctl.go index aae91e4d..69047eda 100644 --- a/controller/wip_ctl.go +++ b/controller/wip_ctl.go @@ -4,10 +4,8 @@ import ( "bytes" "context" "encoding/hex" - "errors" "net/http" "strings" - "time" "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/auth" @@ -51,50 +49,27 @@ func (wipCtl WipController) GetWip(ctx context.Context, w *api.JiaozifsResponse, return } - ref, err := wipCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(params.RefName)) + workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repository, wipCtl.Repo, wipCtl.PublicStorageConfig) if err != nil { w.Error(err) return } - wip, err := wipCtl.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetRefID(ref.ID).SetCreatorID(operator.ID).SetRepositoryID(repository.ID)) - if err == nil { - w.JSON(wip) - return - } - - if err != nil && !errors.Is(err, models.ErrNotFound) { + err = workRepo.CheckOut(ctx, versionmgr.InBranch, params.RefName) + if err != nil { w.Error(err) return } - // if not found create a wip - currentTreeHash := hash.EmptyHash - if !ref.CommitHash.IsEmpty() { - baseCommit, err := wipCtl.Repo.CommitRepo(repository.ID).Commit(ctx, ref.CommitHash) - if err != nil { - w.Error(err) - return - } - currentTreeHash = baseCommit.TreeHash - } - - wip = &models.WorkingInProcess{ - CurrentTree: currentTreeHash, - BaseCommit: ref.CommitHash, - RepositoryID: repository.ID, - RefID: ref.ID, - State: 0, - CreatorID: operator.ID, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - wip, err = wipCtl.Repo.WipRepo().Insert(ctx, wip) + wip, isNew, err := workRepo.GetOrCreateWip(ctx) if err != nil { w.Error(err) return } - w.JSON(wip, http.StatusCreated) + if isNew { + w.JSON(wip, http.StatusCreated) + } + w.JSON(wip) } // ListWip return wips of branches, operator only see himself wips in specific repository @@ -202,28 +177,23 @@ func (wipCtl WipController) DeleteWip(ctx context.Context, w *api.JiaozifsRespon return } - ref, err := wipCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(params.RefName)) + workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repository, wipCtl.Repo, wipCtl.PublicStorageConfig) if err != nil { w.Error(err) return } - deleteWipParams := models.NewDeleteWipParams(). - SetCreatorID(operator.ID). //todo admin delete - SetRepositoryID(repository.ID). - SetRefID(ref.ID) - - affectedRaw, err := wipCtl.Repo.WipRepo().Delete(ctx, deleteWipParams) + err = workRepo.CheckOut(ctx, versionmgr.InBranch, params.RefName) if err != nil { w.Error(err) return } - if affectedRaw == 0 { - w.NotFound() + err = workRepo.DeleteWip(ctx) + if err != nil { + w.Error(err) return } - w.OK() } @@ -323,8 +293,8 @@ func (wipCtl WipController) GetWipChanges(ctx context.Context, w *api.JiaozifsRe w.JSON(changesResp) } -// RevertWip -func (wipCtl WipController) RevertWip(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, ownerName string, repositoryName string, params api.RevertWipParams) { +// RevertWip revert wip changes to base commit +func (wipCtl WipController) RevertWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.RevertWipParams) { operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) diff --git a/integrationtest/commit_test.go b/integrationtest/commit_test.go index 0e4c3d3a..116d687c 100644 --- a/integrationtest/commit_test.go +++ b/integrationtest/commit_test.go @@ -227,7 +227,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { uploadObject(ctx, c, client, "update f2 to main branch", userName, repoName, "main", "g/m.dat") //modify commitWip(ctx, c, client, "commit branch change", userName, repoName, "main", "test") - c.Convey("difference", func(c convey.C) { + c.Convey("compare commit", func(c convey.C) { c.Convey("get base and head", func() { resp, err := client.GetBranch(ctx, userName, repoName, &api.GetBranchParams{RefName: "main"}) convey.So(err, convey.ShouldBeNil) @@ -246,7 +246,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors client.RequestEditors = nil - resp, err := client.GetCommitDiff(ctx, userName, repoName, utils.StringValue(baseHead), &api.GetCommitDiffParams{ + resp, err := client.CompareCommit(ctx, userName, repoName, utils.StringValue(baseHead), &api.CompareCommitParams{ Path: utils.String("/"), }) client.RequestEditors = re @@ -255,7 +255,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("fail to diff in non exit user", func() { - resp, err := client.GetCommitDiff(ctx, "mockuser", repoName, utils.StringValue(baseHead), &api.GetCommitDiffParams{ + resp, err := client.CompareCommit(ctx, "mockuser", repoName, utils.StringValue(baseHead), &api.CompareCommitParams{ Path: utils.String("/"), }) convey.So(err, convey.ShouldBeNil) @@ -263,7 +263,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("fail to diff in non exit repo", func() { - resp, err := client.GetCommitDiff(ctx, userName, "fakerepo", utils.StringValue(baseHead), &api.GetCommitDiffParams{ + resp, err := client.CompareCommit(ctx, userName, "fakerepo", utils.StringValue(baseHead), &api.CompareCommitParams{ Path: utils.String("/"), }) convey.So(err, convey.ShouldBeNil) @@ -271,7 +271,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("forbidden diff in others", func() { - resp, err := client.GetCommitDiff(ctx, "jimmy", "happygo", utils.StringValue(baseHead), &api.GetCommitDiffParams{ + resp, err := client.CompareCommit(ctx, "jimmy", "happygo", utils.StringValue(baseHead), &api.CompareCommitParams{ Path: utils.String("/"), }) convey.So(err, convey.ShouldBeNil) @@ -279,7 +279,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("not exit path", func() { - resp, err := client.GetCommitDiff(ctx, userName, repoName, utils.StringValue(baseHead), &api.GetCommitDiffParams{ + resp, err := client.CompareCommit(ctx, userName, repoName, utils.StringValue(baseHead), &api.CompareCommitParams{ Path: utils.String("/a/b/c/d"), }) convey.So(err, convey.ShouldBeNil) @@ -287,13 +287,13 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("success to diff", func() { - resp, err := client.GetCommitDiff(ctx, userName, repoName, utils.StringValue(baseHead), &api.GetCommitDiffParams{ + resp, err := client.CompareCommit(ctx, userName, repoName, utils.StringValue(baseHead), &api.CompareCommitParams{ Path: utils.String("/"), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) - result, err := api.ParseGetCommitDiffResponse(resp) + result, err := api.ParseCompareCommitResponse(resp) convey.So(err, convey.ShouldBeNil) convey.So(*result.JSON200, convey.ShouldHaveLength, 4) convey.So((*result.JSON200)[0].Path, convey.ShouldEqual, "a.dat") @@ -308,3 +308,104 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { }) } } + +func GetCommitChangesSpec(ctx context.Context, urlStr string) func(c convey.C) { + client, _ := api.NewClient(urlStr + apiimpl.APIV1Prefix) + var commits []api.Commit + return func(c convey.C) { + userName := "kelly" + repoName := "gcc" + + createUser(ctx, c, client, userName) + loginAndSwitch(ctx, c, client, "getCommitChanges login", userName, false) + createRepo(ctx, c, client, repoName) + createWip(ctx, c, client, "feat get entries test0", userName, repoName, "main") + uploadObject(ctx, c, client, "update f1 to test branch", userName, repoName, "main", "m.dat") + commitWip(ctx, c, client, "commit kelly first changes", userName, repoName, "main", "test") + + uploadObject(ctx, c, client, "update f2 to test branch", userName, repoName, "main", "g/x.dat") + commitWip(ctx, c, client, "commit kelly second changes", userName, repoName, "main", "test") + + uploadObject(ctx, c, client, "update f3 to test branch", userName, repoName, "main", "g/m.dat") + commitWip(ctx, c, client, "commit kelly third changes", userName, repoName, "main", "test") + + c.Convey("get commit change", func(c convey.C) { + c.Convey("list commit history", func() { + resp, err := client.GetCommitsInRepository(ctx, userName, repoName, &api.GetCommitsInRepositoryParams{RefName: utils.String("main")}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + result, err := api.ParseGetCommitsInRepositoryResponse(resp) + convey.So(err, convey.ShouldBeNil) + + commits = *result.JSON200 + }) + + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.GetCommitChanges(ctx, userName, repoName, commits[2].Hash, &api.GetCommitChangesParams{ + Path: utils.String("/"), + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("fail to get commit changes in non exit user", func() { + resp, err := client.GetCommitChanges(ctx, "mockuser", repoName, commits[2].Hash, &api.GetCommitChangesParams{ + Path: utils.String("/"), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to get commit changes in non exit repo", func() { + resp, err := client.GetCommitChanges(ctx, userName, "fakerepo", commits[2].Hash, &api.GetCommitChangesParams{ + Path: utils.String("/"), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("forbidden get commit changes in others", func() { + resp, err := client.GetCommitChanges(ctx, "jimmy", "happygo", commits[2].Hash, &api.GetCommitChangesParams{ + Path: utils.String("/"), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + + c.Convey("not exit path", func() { + resp, err := client.GetCommitChanges(ctx, userName, repoName, commits[2].Hash, &api.GetCommitChangesParams{ + Path: utils.String("/a/b/c/d"), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + }) + + c.Convey("success to get first changes", func() { + resp, err := client.GetCommitChanges(ctx, userName, repoName, commits[0].Hash, &api.GetCommitChangesParams{ + Path: utils.String("/"), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseCompareCommitResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(*result.JSON200, convey.ShouldHaveLength, 1) + }) + + c.Convey("success to get first commit changes", func() { + resp, err := client.GetCommitChanges(ctx, userName, repoName, commits[2].Hash, &api.GetCommitChangesParams{ + Path: utils.String("/"), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseCompareCommitResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(*result.JSON200, convey.ShouldHaveLength, 1) + }) + }) + } +} diff --git a/integrationtest/root_test.go b/integrationtest/root_test.go index 41017f7a..d9065f27 100644 --- a/integrationtest/root_test.go +++ b/integrationtest/root_test.go @@ -19,4 +19,6 @@ func TestSpec(t *testing.T) { convey.Convey("object test", t, ObjectSpec(ctx, urlStr)) convey.Convey("wip object test", t, WipObjectSpec(ctx, urlStr)) convey.Convey("commit test", t, GetEntriesInRefSpec(ctx, urlStr)) + convey.Convey("commit test", t, GetCommitChangesSpec(ctx, urlStr)) + } diff --git a/integrationtest/wip_test.go b/integrationtest/wip_test.go index c2c09b6e..8f77de44 100644 --- a/integrationtest/wip_test.go +++ b/integrationtest/wip_test.go @@ -192,7 +192,7 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { func createWip(ctx context.Context, c convey.C, client *api.Client, title string, user string, repoName string, refName string) { c.Convey("create wip "+title, func() { - resp, err := client.CreateWip(ctx, user, repoName, &api.CreateWipParams{ + resp, err := client.GetWip(ctx, user, repoName, &api.GetWipParams{ RefName: refName, }) convey.So(err, convey.ShouldBeNil) diff --git a/models/models.go b/models/models.go index bb73b275..6042c0f1 100644 --- a/models/models.go +++ b/models/models.go @@ -21,7 +21,9 @@ func SetupDatabase(ctx context.Context, lc fx.Lifecycle, dbConfig *config.Databa bunDB := bun.NewDB(sqlDB, pgdialect.New(), bun.WithDiscardUnknownColumns()) - bunDB.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true))) + if dbConfig.Debug { + bunDB.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true))) + } lc.Append(fx.Hook{ OnStop: func(ctx context.Context) error { diff --git a/utils/hash/hash.go b/utils/hash/hash.go index a0f2276d..046d949a 100644 --- a/utils/hash/hash.go +++ b/utils/hash/hash.go @@ -16,7 +16,17 @@ var EmptyHash = Hash{} type Hash []byte +func FromHex(str string) (Hash, error) { + if len(str) == 0 { + return EmptyHash, nil + } + return hex.DecodeString(str) +} + func (hash Hash) Hex() string { + if hash == nil { + hex.EncodeToString(EmptyHash) + } return hex.EncodeToString(hash) } @@ -39,7 +49,7 @@ func (hash *Hash) UnmarshalJSON(bytes []byte) error { return nil } - hexData, err := hex.DecodeString(string(bytes[1 : len(bytes)-1])) + hexData, err := FromHex(string(bytes[1 : len(bytes)-1])) if err != nil { return err } @@ -57,7 +67,7 @@ func (hash Hash) MarshalJSON() ([]byte, error) { func HashesOfHexArray(hashesStr ...string) ([]Hash, error) { hashes := make([]Hash, len(hashesStr)) for i, hashStr := range hashesStr { - hash, err := hex.DecodeString(hashStr) + hash, err := FromHex(hashStr) if err != nil { return nil, err } diff --git a/utils/hash/hash_test.go b/utils/hash/hash_test.go index 120ca540..fd8f5ea6 100644 --- a/utils/hash/hash_test.go +++ b/utils/hash/hash_test.go @@ -44,3 +44,17 @@ func TestHexArrayOfHashes(t *testing.T) { require.NoError(t, err) require.Equal(t, hashes, hashes2) } + +func TestHashFromHex(t *testing.T) { + t.Run("empty", func(t *testing.T) { + hash, err := FromHex("") + require.NoError(t, err) + require.Equal(t, EmptyHash, hash) + }) + + t.Run("data", func(t *testing.T) { + hash, err := FromHex("616161616161") + require.NoError(t, err) + require.Equal(t, Hash("aaaaaa"), hash) + }) +} diff --git a/versionmgr/work_repo.go b/versionmgr/work_repo.go index bc3876d5..062d3d62 100644 --- a/versionmgr/work_repo.go +++ b/versionmgr/work_repo.go @@ -3,7 +3,6 @@ package versionmgr import ( "bytes" "context" - "encoding/hex" "errors" "fmt" "io" @@ -190,16 +189,19 @@ func (repository *WorkRepository) CheckOut(ctx context.Context, refType WorkRepo } repository.setCurState(InBranch, nil, branch, commit) } else if refType == InCommit { - commitHash, err := hex.DecodeString(refName) + commitHash, err := hash.FromHex(refName) if err != nil { return err } - commit, err := repository.repo.CommitRepo(repository.repoModel.ID).Commit(ctx, commitHash) - if err != nil { - return err + + if !commitHash.IsEmpty() { + commit, err := repository.repo.CommitRepo(repository.repoModel.ID).Commit(ctx, commitHash) + if err != nil { + return err + } + treeHash = commit.TreeHash + repository.setCurState(InCommit, nil, nil, commit) } - treeHash = commit.TreeHash - repository.setCurState(InCommit, nil, nil, commit) } else { return fmt.Errorf("not support type") } @@ -227,6 +229,23 @@ func (repository *WorkRepository) RevertWip(ctx context.Context) error { return nil } +// DeleteWip remove wip todo remove files +func (repository *WorkRepository) DeleteWip(ctx context.Context) error { + if repository.state != InBranch { + return fmt.Errorf("working repo not in branch state") + } + + deleteParams := models.NewDeleteWipParams().SetRefID(repository.branch.ID).SetRepositoryID(repository.repoModel.ID).SetCreatorID(repository.operator.ID) + affectRow, err := repository.repo.WipRepo().Delete(ctx, deleteParams) + if err != nil { + return err + } + if affectRow == 0 { + return models.ErrNotFound + } + return nil +} + // CommitChanges append a new commit to current headTree, read changes from wip, than create a new commit with parent point to current headTree, // and replace tree hash with wip's currentTreeHash. func (repository *WorkRepository) CommitChanges(ctx context.Context, msg string) (*models.Commit, error) { @@ -298,6 +317,94 @@ func (repository *WorkRepository) CommitChanges(ctx context.Context, msg string) return commit, err } +// CreateBranch create branch base on current head +func (repository *WorkRepository) CreateBranch(ctx context.Context, branchName string) (*models.Branch, error) { + //check exit + _, err := repository.repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetName(branchName).SetRepositoryID(repository.repoModel.ID)) + if err == nil { + return nil, fmt.Errorf("%s already exit", branchName) + } + if err != nil && !errors.Is(err, models.ErrNotFound) { + return nil, err + } + + commitHash := hash.EmptyHash + if repository.commit != nil { + commitHash = repository.commit.Hash + } + // Create branch + newBranch := &models.Branch{ + RepositoryID: repository.repoModel.ID, + CommitHash: commitHash, + Name: branchName, + CreatorID: repository.operator.ID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + return repository.repo.BranchRepo().Insert(ctx, newBranch) +} + +// DeleteBranch delete branch also delete wip belong this branch +func (repository *WorkRepository) DeleteBranch(ctx context.Context) error { + return repository.repo.Transaction(ctx, func(repo models.IRepo) error { + deleteBranchParams := models.NewDeleteBranchParams(). + SetRepositoryID(repository.repoModel.ID). + SetName(repository.branch.Name) + _, err := repo.BranchRepo().Delete(ctx, deleteBranchParams) + if err != nil { + return err + } + + deleteWipParams := models.NewDeleteWipParams().SetRepositoryID(repository.repoModel.ID).SetRefID(repository.branch.ID) + _, err = repo.WipRepo().Delete(ctx, deleteWipParams) + return err + }) +} + +func (repository *WorkRepository) GetOrCreateWip(ctx context.Context) (*models.WorkingInProcess, bool, error) { + if repository.state != InBranch { + return nil, false, fmt.Errorf("only create wip from branch") + } + + wip, err := repository.repo.WipRepo().Get(ctx, models.NewGetWipParams().SetRefID(repository.branch.ID).SetCreatorID(repository.operator.ID).SetRepositoryID(repository.repoModel.ID)) + if err == nil { + repository.headTree = &wip.CurrentTree + return wip, false, nil + } + + if err != nil && !errors.Is(err, models.ErrNotFound) { + return nil, false, err + } + + // if not found create a wip + currentTreeHash := hash.EmptyHash + if !repository.branch.CommitHash.IsEmpty() { + baseCommit, err := repository.repo.CommitRepo(repository.repoModel.ID).Commit(ctx, repository.branch.CommitHash) + if err != nil { + return nil, false, err + } + currentTreeHash = baseCommit.TreeHash + } + + wip = &models.WorkingInProcess{ + CurrentTree: currentTreeHash, + BaseCommit: repository.branch.CommitHash, + RepositoryID: repository.repoModel.ID, + RefID: repository.branch.ID, + State: 0, + CreatorID: repository.operator.ID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + wip, err = repository.repo.WipRepo().Insert(ctx, wip) + if err != nil { + return nil, false, err + } + repository.headTree = &wip.CurrentTree + repository.setCurState(InWip, wip, repository.branch, nil) + return wip, true, nil +} + // DiffCommit find file changes in two commit func (repository *WorkRepository) DiffCommit(ctx context.Context, toCommitID hash.Hash) (*Changes, error) { workTree, err := repository.RootTree(ctx) @@ -312,6 +419,19 @@ func (repository *WorkRepository) DiffCommit(ctx context.Context, toCommitID has return workTree.Diff(ctx, toCommit.TreeHash) } +func (repository *WorkRepository) GetCommitChanges(ctx context.Context) (*Changes, error) { + if len(repository.commit.ParentHashes) == 0 { + workTree, err := repository.RootTree(ctx) + if err != nil { + return nil, err + } + return workTree.Diff(ctx, hash.EmptyHash) + } else if len(repository.commit.ParentHashes) == 1 { + return repository.DiffCommit(ctx, repository.commit.ParentHashes[0]) + } + return repository.DiffCommit(ctx, repository.commit.ParentHashes[1]) +} + // Merge implement merge like git, docs https://en.wikipedia.org/wiki/Merge_(version_control) func (repository *WorkRepository) Merge(ctx context.Context, merger *models.User, toMergeCommitHash hash.Hash, msg string, resolver ConflictResolver) (*models.Commit, error) { if repository.state != InBranch { diff --git a/versionmgr/work_repo_diff_test.go b/versionmgr/work_repo_diff_test.go index 5c7f488d..693fd6ab 100644 --- a/versionmgr/work_repo_diff_test.go +++ b/versionmgr/work_repo_diff_test.go @@ -25,7 +25,6 @@ func TestWorkRepositoryDiffCommit(t *testing.T) { project, err := makeRepository(ctx, repo.RepositoryRepo(), user, "testproject") require.NoError(t, err) - //commit1 a.txt b/c.txt b/e.txt //commit2 a.txt b/d.txt b/e.txt testData1 := ` @@ -33,15 +32,20 @@ func TestWorkRepositoryDiffCommit(t *testing.T) { 1|b/c.txt |c 1|b/e.txt |e1 ` + + workRepo := NewWorkRepositoryFromAdapter(ctx, user, project, repo, adapter) //base branch - baseBranch, err := makeBranch(ctx, repo.BranchRepo(), user, "feat/base", project.ID, hash.EmptyHash) + + err = workRepo.CheckOut(ctx, InCommit, hash.EmptyHash.Hex()) require.NoError(t, err) + baseBranch, err := workRepo.CreateBranch(ctx, "feat/base") + require.NoError(t, err) + root1, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), EmptyDirEntry, testData1) require.NoError(t, err) baseWip, err := makeWip(ctx, repo.WipRepo(), user.ID, project.ID, baseBranch.ID, EmptyRoot.Hash, root1.Hash) require.NoError(t, err) - workRepo := NewWorkRepositoryFromAdapter(ctx, user, project, repo, adapter) _, err = workRepo.CommitChanges(ctx, "base commit") //asset not correct state require.Error(t, err) require.NoError(t, workRepo.CheckOut(ctx, InWip, "feat/base")) From d22c66e1dea77f52605c485f7e59a7be075473a8 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Mon, 1 Jan 2024 10:45:01 +0800 Subject: [PATCH 133/210] feat: add api for revert changes --- api/jiaozifs.gen.go | 149 ++++++++++++++++++------------ api/swagger.yml | 9 +- controller/commit_ctl.go | 52 +---------- controller/helper.go | 36 ++++++++ controller/wip_ctl.go | 36 +------- makefile | 2 + versionmgr/changes.go | 14 +-- versionmgr/merkletrie/change.go | 8 ++ versionmgr/work_repo.go | 106 +++++++++++++++++---- versionmgr/work_repo_diff_test.go | 2 +- versionmgr/worktree.go | 14 ++- 11 files changed, 254 insertions(+), 174 deletions(-) create mode 100644 controller/helper.go diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 6a4d1d8a..573e8956 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -473,10 +473,13 @@ type CommitWipParams struct { RefName string `form:"refName" json:"refName"` } -// RevertWipParams defines parameters for RevertWip. -type RevertWipParams struct { +// RevertWipChangesParams defines parameters for RevertWipChanges. +type RevertWipChangesParams struct { // RefName ref name RefName string `form:"refName" json:"refName"` + + // PathPrefix prefix of path + PathPrefix *string `form:"pathPrefix,omitempty" json:"pathPrefix,omitempty"` } // LoginJSONRequestBody defines body for Login for application/json ContentType. @@ -667,8 +670,8 @@ type ClientInterface interface { // ListWip request ListWip(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) - // RevertWip request - RevertWip(ctx context.Context, owner string, repository string, params *RevertWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // RevertWipChanges request + RevertWipChanges(ctx context.Context, owner string, repository string, params *RevertWipChangesParams, reqEditors ...RequestEditorFn) (*http.Response, error) } func (c *Client) LoginWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -1079,8 +1082,8 @@ func (c *Client) ListWip(ctx context.Context, owner string, repository string, r return c.Client.Do(req) } -func (c *Client) RevertWip(ctx context.Context, owner string, repository string, params *RevertWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewRevertWipRequest(c.Server, owner, repository, params) +func (c *Client) RevertWipChanges(ctx context.Context, owner string, repository string, params *RevertWipChangesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRevertWipChangesRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -2862,8 +2865,8 @@ func NewListWipRequest(server string, owner string, repository string) (*http.Re return req, nil } -// NewRevertWipRequest generates requests for RevertWip -func NewRevertWipRequest(server string, owner string, repository string, params *RevertWipParams) (*http.Request, error) { +// NewRevertWipChangesRequest generates requests for RevertWipChanges +func NewRevertWipChangesRequest(server string, owner string, repository string, params *RevertWipChangesParams) (*http.Request, error) { var err error var pathParam0 string @@ -2910,6 +2913,22 @@ func NewRevertWipRequest(server string, owner string, repository string, params } } + if params.PathPrefix != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "pathPrefix", runtime.ParamLocationQuery, *params.PathPrefix); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + queryURL.RawQuery = queryValues.Encode() } @@ -3061,8 +3080,8 @@ type ClientWithResponsesInterface interface { // ListWipWithResponse request ListWipWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*ListWipResponse, error) - // RevertWipWithResponse request - RevertWipWithResponse(ctx context.Context, owner string, repository string, params *RevertWipParams, reqEditors ...RequestEditorFn) (*RevertWipResponse, error) + // RevertWipChangesWithResponse request + RevertWipChangesWithResponse(ctx context.Context, owner string, repository string, params *RevertWipChangesParams, reqEditors ...RequestEditorFn) (*RevertWipChangesResponse, error) } type LoginResponse struct { @@ -3694,13 +3713,13 @@ func (r ListWipResponse) StatusCode() int { return 0 } -type RevertWipResponse struct { +type RevertWipChangesResponse struct { Body []byte HTTPResponse *http.Response } // Status returns HTTPResponse.Status -func (r RevertWipResponse) Status() string { +func (r RevertWipChangesResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3708,7 +3727,7 @@ func (r RevertWipResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r RevertWipResponse) StatusCode() int { +func (r RevertWipChangesResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -4016,13 +4035,13 @@ func (c *ClientWithResponses) ListWipWithResponse(ctx context.Context, owner str return ParseListWipResponse(rsp) } -// RevertWipWithResponse request returning *RevertWipResponse -func (c *ClientWithResponses) RevertWipWithResponse(ctx context.Context, owner string, repository string, params *RevertWipParams, reqEditors ...RequestEditorFn) (*RevertWipResponse, error) { - rsp, err := c.RevertWip(ctx, owner, repository, params, reqEditors...) +// RevertWipChangesWithResponse request returning *RevertWipChangesResponse +func (c *ClientWithResponses) RevertWipChangesWithResponse(ctx context.Context, owner string, repository string, params *RevertWipChangesParams, reqEditors ...RequestEditorFn) (*RevertWipChangesResponse, error) { + rsp, err := c.RevertWipChanges(ctx, owner, repository, params, reqEditors...) if err != nil { return nil, err } - return ParseRevertWipResponse(rsp) + return ParseRevertWipChangesResponse(rsp) } // ParseLoginResponse parses an HTTP response from a LoginWithResponse call @@ -4689,15 +4708,15 @@ func ParseListWipResponse(rsp *http.Response) (*ListWipResponse, error) { return response, nil } -// ParseRevertWipResponse parses an HTTP response from a RevertWipWithResponse call -func ParseRevertWipResponse(rsp *http.Response) (*RevertWipResponse, error) { +// ParseRevertWipChangesResponse parses an HTTP response from a RevertWipChangesWithResponse call +func ParseRevertWipChangesResponse(rsp *http.Response) (*RevertWipChangesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &RevertWipResponse{ + response := &RevertWipChangesResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -4794,9 +4813,9 @@ type ServerInterface interface { // list wip in specific project and user // (GET /wip/{owner}/{repository}/list) ListWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) - // revert working in process + // revert changes in working in process, empty path will revert all // (POST /wip/{owner}/{repository}/revert) - RevertWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params RevertWipParams) + RevertWipChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params RevertWipChangesParams) } // Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. @@ -4976,9 +4995,9 @@ func (_ Unimplemented) ListWip(ctx context.Context, w *JiaozifsResponse, r *http w.WriteHeader(http.StatusNotImplemented) } -// revert working in process +// revert changes in working in process, empty path will revert all // (POST /wip/{owner}/{repository}/revert) -func (_ Unimplemented) RevertWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params RevertWipParams) { +func (_ Unimplemented) RevertWipChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params RevertWipChangesParams) { w.WriteHeader(http.StatusNotImplemented) } @@ -6556,8 +6575,8 @@ func (siw *ServerInterfaceWrapper) ListWip(w http.ResponseWriter, r *http.Reques handler.ServeHTTP(w, r.WithContext(ctx)) } -// RevertWip operation middleware -func (siw *ServerInterfaceWrapper) RevertWip(w http.ResponseWriter, r *http.Request) { +// RevertWipChanges operation middleware +func (siw *ServerInterfaceWrapper) RevertWipChanges(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -6587,7 +6606,7 @@ func (siw *ServerInterfaceWrapper) RevertWip(w http.ResponseWriter, r *http.Requ ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context - var params RevertWipParams + var params RevertWipChangesParams // ------------- Required query parameter "refName" ------------- @@ -6604,8 +6623,16 @@ func (siw *ServerInterfaceWrapper) RevertWip(w http.ResponseWriter, r *http.Requ return } + // ------------- Optional query parameter "pathPrefix" ------------- + + err = runtime.BindQueryParameter("form", true, false, "pathPrefix", r.URL.Query(), ¶ms.PathPrefix) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "pathPrefix", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.RevertWip(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + siw.Handler.RevertWipChanges(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -6821,7 +6848,7 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Get(options.BaseURL+"/wip/{owner}/{repository}/list", wrapper.ListWip) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/wip/{owner}/{repository}/revert", wrapper.RevertWip) + r.Post(options.BaseURL+"/wip/{owner}/{repository}/revert", wrapper.RevertWipChanges) }) return r @@ -6831,7 +6858,7 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl var swaggerSpec = []string{ "H4sIAAAAAAAC/+xd63Pbtpb/VzDc+6HdpSzZTjt73encSXyTJrtO67Gd5kPs1UDkoYSGBHgB0LKa8f++", - "gwffIEXJsiPn9osnIvE4OI8fzjk4YL54AUtSRoFK4Z188VLMcQISuP51jueEYkkYfZmwjEr1LAQRcJKq", + "gwffIEXJsiPn9ksmIvE4OI8fzjk4oL94AUtSRoFK4Z188VLMcQISuP51jueEYkkYfZmwjEr1LAQRcJKq", "h96Jt2BLlGC6QkRCIpBkiIPMOPV8j6j3/8qArzzfozgB78TDZhjfE8ECEmzGi3AWS+/kcDLxvQTfkSRL", "9C/1k1Dzc3Toe3KVqjEIlTAH7t3f+xUCX3FMg8XLSAJvU2losjRi1QbJBRHoFscZdJGqh6pSaucXkhM6", "b0x/ypKEyEedPmI8wdI78UIsYSRJAiMqPL+XrHMOEblbQ1GqG0GIlkQu1lNmmg/mzAWk7In54mDKfd5D", @@ -6848,7 +6875,7 @@ var swaggerSpec = []string{ "jCpvX1lKKYAIx/EMB5+nsVrTNMYziNvU68fKS0xjHICiudEv4/GBt374jDsGNw4i5iv04eJMTcKiCLhy", "TLmOzTIBKGIc6SGcs5jBA8Y+E5gqMYv2LOYt0m8Lp1dbtXKNlUIMRlMzXYRJDOG0gtj1Ce0LNU1IRBrj", "lV0MF2i5YEj1V0/0aD8hjKIsjpEAKoEGYLx0IhAHGgKH8JoSit5evT9DmIYowSsFM1JpEkYxoZ+1D49K", - "XuphUQJywcJr2s01p0hSTpKKQAZJgGXSPVh7kDmhc8QyebDWZksanVKuTeyy1N/0vy4ltomCOvwtIPgs", + "XuphUQJywcJr2s01p0hSTpKKQAZJgGXSPVh7kDmhc8QyebDWZksanVKuTeyy1N/0/y4ltomCOvwtIPgs", "lMW4QgWmBCGn5kVzTWZclEBIMNJNfNdmLnGIJV7nbJjBPgjg7/MeqrcGtd0FVz3OmnoxTVhYh+KMUHl8", "5BxJYeZ0tpKGj5vGdQXf/dz70wRYNpp1dwuzxiflBoYhUbzB8Xk9Eu4w43K885pXX9eNBRbThHGHAH6F", "O4lSZdlEIHyLSayAvFx1ZdtL8N00BT5NnQDxHt+RBMeIZskMOGIRAio5AYFS4HoGr5JLmrjkQOFOTlkU", @@ -6870,36 +6897,36 @@ var swaggerSpec = []string{ "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", - "///hLh6kxGsAAA==", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index b4e7f3e2..0ede14b9 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -1051,11 +1051,16 @@ paths: required: true schema: type: string + - in: query + name: pathPrefix + description: prefix of path + schema: + type: string post: tags: - wip - operationId: revertWip - summary: revert working in process + operationId: revertWipChanges + summary: revert changes in working in process, empty path will revert all responses: 200: description: success to revert wip diff --git a/controller/commit_ctl.go b/controller/commit_ctl.go index e38f8a21..3b0179be 100644 --- a/controller/commit_ctl.go +++ b/controller/commit_ctl.go @@ -154,35 +154,13 @@ func (commitCtl CommitController) CompareCommit(ctx context.Context, w *api.Jiao return } - changes, err := workRepo.DiffCommit(ctx, toCommitHash) + changes, err := workRepo.DiffCommit(ctx, toCommitHash, utils.StringValue(params.Path)) if err != nil { w.Error(err) return } - path := versionmgr.CleanPath(utils.StringValue(params.Path)) - var changesResp []api.Change - err = changes.ForEach(func(change versionmgr.IChange) error { - action, err := change.Action() - if err != nil { - return err - } - fullPath := change.Path() - if strings.HasPrefix(fullPath, path) { - apiChange := api.Change{ - Action: api.ChangeAction(action), - Path: fullPath, - } - if change.From() != nil { - apiChange.BaseHash = utils.String(hex.EncodeToString(change.From().Hash())) - } - if change.To() != nil { - apiChange.ToHash = utils.String(hex.EncodeToString(change.To().Hash())) - } - changesResp = append(changesResp, apiChange) - } - return nil - }) + changesResp, err := changesToDTO(changes) if err != nil { w.Error(err) return @@ -226,35 +204,13 @@ func (commitCtl CommitController) GetCommitChanges(ctx context.Context, w *api.J return } - changes, err := workRepo.GetCommitChanges(ctx) + changes, err := workRepo.GetCommitChanges(ctx, utils.StringValue(params.Path)) if err != nil { w.Error(err) return } - path := versionmgr.CleanPath(utils.StringValue(params.Path)) - var changesResp []api.Change - err = changes.ForEach(func(change versionmgr.IChange) error { - action, err := change.Action() - if err != nil { - return err - } - fullPath := change.Path() - if strings.HasPrefix(fullPath, path) { - apiChange := api.Change{ - Action: api.ChangeAction(action), - Path: fullPath, - } - if change.From() != nil { - apiChange.BaseHash = utils.String(hex.EncodeToString(change.From().Hash())) - } - if change.To() != nil { - apiChange.ToHash = utils.String(hex.EncodeToString(change.To().Hash())) - } - changesResp = append(changesResp, apiChange) - } - return nil - }) + changesResp, err := changesToDTO(changes) if err != nil { w.Error(err) return diff --git a/controller/helper.go b/controller/helper.go new file mode 100644 index 00000000..2a4cbc89 --- /dev/null +++ b/controller/helper.go @@ -0,0 +1,36 @@ +package controller + +import ( + "encoding/hex" + + "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/utils" + "github.com/jiaozifs/jiaozifs/versionmgr" +) + +func changesToDTO(changes *versionmgr.Changes) ([]api.Change, error) { + changesResp := make([]api.Change, 0) + err := changes.ForEach(func(change versionmgr.IChange) error { + action, err := change.Action() + if err != nil { + return err + } + fullPath := change.Path() + apiChange := api.Change{ + Action: api.ChangeAction(action), + Path: fullPath, + } + if change.From() != nil { + apiChange.BaseHash = utils.String(hex.EncodeToString(change.From().Hash())) + } + if change.To() != nil { + apiChange.ToHash = utils.String(hex.EncodeToString(change.To().Hash())) + } + changesResp = append(changesResp, apiChange) + return nil + }) + if err != nil { + return nil, err + } + return changesResp, nil +} diff --git a/controller/wip_ctl.go b/controller/wip_ctl.go index 69047eda..b3914159 100644 --- a/controller/wip_ctl.go +++ b/controller/wip_ctl.go @@ -3,9 +3,7 @@ package controller import ( "bytes" "context" - "encoding/hex" "net/http" - "strings" "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/auth" @@ -255,46 +253,22 @@ func (wipCtl WipController) GetWipChanges(ctx context.Context, w *api.JiaozifsRe return } - changes, err := workTree.Diff(ctx, wip.CurrentTree) + changes, err := workTree.Diff(ctx, wip.CurrentTree, utils.StringValue(params.Path)) if err != nil { w.Error(err) return } - path := versionmgr.CleanPath(utils.StringValue(params.Path)) - - var changesResp []api.Change - err = changes.ForEach(func(change versionmgr.IChange) error { - action, err := change.Action() - if err != nil { - return err - } - fullPath := change.Path() - if strings.HasPrefix(fullPath, path) { - apiChange := api.Change{ - Action: api.ChangeAction(action), - Path: fullPath, - } - if change.From() != nil { - apiChange.BaseHash = utils.String(hex.EncodeToString(change.From().Hash())) - } - if change.To() != nil { - apiChange.ToHash = utils.String(hex.EncodeToString(change.To().Hash())) - } - changesResp = append(changesResp, apiChange) - } - return nil - }) + changesResp, err := changesToDTO(changes) if err != nil { w.Error(err) return } - w.JSON(changesResp) } -// RevertWip revert wip changes to base commit -func (wipCtl WipController) RevertWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.RevertWipParams) { +// RevertWipChanges revert wip changes, if path is empty, revert all +func (wipCtl WipController) RevertWipChanges(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.RevertWipChangesParams) { operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) @@ -330,7 +304,7 @@ func (wipCtl WipController) RevertWip(ctx context.Context, w *api.JiaozifsRespon return } - err = workRepo.RevertWip(ctx) + err = workRepo.Revert(ctx, utils.StringValue(params.PathPrefix)) if err != nil { w.Error(err) return diff --git a/makefile b/makefile index bf2223e9..346c6f6d 100644 --- a/makefile +++ b/makefile @@ -23,5 +23,7 @@ SWAGGER_ARG= swagger-srv: swagger serve $(SWAGGER_ARG) -F swagger ./api/swagger.yml +test: gen-api + go test -timeout=30m -parallel=4 -v ./... build:gen-api go build $(GOFLAGS) -o jzfs diff --git a/versionmgr/changes.go b/versionmgr/changes.go index 446a4f11..c494b97a 100644 --- a/versionmgr/changes.go +++ b/versionmgr/changes.go @@ -44,19 +44,7 @@ func (c *Change) To() noder.Path { // Path return change path func (c *Change) Path() string { - action, err := c.Action() - if err != nil { - panic(err) - } - - var path string - if action == merkletrie.Delete { - path = c.Change.From.String() - } else { - path = c.Change.To.String() - } - - return path + return c.Change.Path() } // Changes used to recored changes between commit, also provider iter function diff --git a/versionmgr/merkletrie/change.go b/versionmgr/merkletrie/change.go index c1b352d5..1d9de000 100644 --- a/versionmgr/merkletrie/change.go +++ b/versionmgr/merkletrie/change.go @@ -56,6 +56,14 @@ func (c *Change) Action() (Action, error) { return Modify, nil } +// Path returns the path of the change. +func (c *Change) Path() string { + if c.From != nil { + return c.From.String() + } + return c.To.String() +} + // NewInsert returns a new Change representing the insertion of n. func NewInsert(n noder.Path) Change { return Change{To: n} } diff --git a/versionmgr/work_repo.go b/versionmgr/work_repo.go index 062d3d62..58602fa6 100644 --- a/versionmgr/work_repo.go +++ b/versionmgr/work_repo.go @@ -9,6 +9,8 @@ import ( "os" "time" + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" + "github.com/jiaozifs/jiaozifs/utils" logging "github.com/ipfs/go-log/v2" @@ -209,24 +211,96 @@ func (repository *WorkRepository) CheckOut(ctx context.Context, refType WorkRepo return nil } -func (repository *WorkRepository) RevertWip(ctx context.Context) error { +func (repository *WorkRepository) Revert(ctx context.Context, prefixPath string) error { if repository.state != InWip { return fmt.Errorf("working repo not in wip state") } - treeHash := hash.EmptyHash + + baseTreeHash := hash.EmptyHash if !repository.wip.BaseCommit.IsEmpty() { commit, err := repository.repo.CommitRepo(repository.repoModel.ID).Commit(ctx, repository.wip.BaseCommit) if err != nil { return err } - treeHash = commit.TreeHash + baseTreeHash = commit.TreeHash } - err := repository.repo.WipRepo().UpdateByID(ctx, models.NewUpdateWipParams(repository.wip.ID).SetCurrentTree(treeHash)) - if err != nil { - return err + + prefixPath = CleanPath(prefixPath) + if len(prefixPath) == 0 { + //just revert all + err := repository.repo.WipRepo().UpdateByID(ctx, models.NewUpdateWipParams(repository.wip.ID).SetCurrentTree(baseTreeHash)) + if err != nil { + return err + } + repository.wip.CurrentTree = baseTreeHash + return nil } - repository.wip.CurrentTree = treeHash - return nil + + err := repository.repo.Transaction(ctx, func(repo models.IRepo) error { + baseTree, err := NewWorkTree(ctx, repo.FileTreeRepo(repository.repoModel.ID), models.NewRootTreeEntry(baseTreeHash)) + if err != nil { + return err + } + curTree, err := NewWorkTree(ctx, repo.FileTreeRepo(repository.repoModel.ID), models.NewRootTreeEntry(repository.wip.CurrentTree)) + if err != nil { + return err + } + + changes, err := curTree.Diff(ctx, baseTreeHash, prefixPath) + if err != nil { + return err + } + if changes.Num() == 0 { + return models.ErrNotFound + } + + err = changes.ForEach(func(change IChange) error { + action, err := change.Action() + if err != nil { + return err + } + changePath := change.Path() + switch action { + case merkletrie.Insert: + err = curTree.RemoveEntry(ctx, prefixPath) + if err != nil { + return err + } + case merkletrie.Modify: + blob, _, err := baseTree.FindBlob(ctx, changePath) + if err != nil { + return err + } + + err = curTree.ReplaceLeaf(ctx, prefixPath, blob) + if err != nil { + return err + } + case merkletrie.Delete: + blob, _, err := baseTree.FindBlob(ctx, changePath) + if err != nil { + return err + } + + err = curTree.AddLeaf(ctx, prefixPath, blob) + if err != nil { + return err + } + } + return nil + }) + if err != nil { + return err + } + + err = repository.repo.WipRepo().UpdateByID(ctx, models.NewUpdateWipParams(repository.wip.ID).SetCurrentTree(curTree.Root().Hash())) + if err != nil { + return err + } + repository.wip.CurrentTree = curTree.Root().Hash() + return nil + }) + return err } // DeleteWip remove wip todo remove files @@ -406,7 +480,7 @@ func (repository *WorkRepository) GetOrCreateWip(ctx context.Context) (*models.W } // DiffCommit find file changes in two commit -func (repository *WorkRepository) DiffCommit(ctx context.Context, toCommitID hash.Hash) (*Changes, error) { +func (repository *WorkRepository) DiffCommit(ctx context.Context, toCommitID hash.Hash, pathPrefix string) (*Changes, error) { workTree, err := repository.RootTree(ctx) if err != nil { return nil, err @@ -416,20 +490,20 @@ func (repository *WorkRepository) DiffCommit(ctx context.Context, toCommitID has return nil, err } - return workTree.Diff(ctx, toCommit.TreeHash) + return workTree.Diff(ctx, toCommit.TreeHash, pathPrefix) } -func (repository *WorkRepository) GetCommitChanges(ctx context.Context) (*Changes, error) { +func (repository *WorkRepository) GetCommitChanges(ctx context.Context, pathPrefix string) (*Changes, error) { if len(repository.commit.ParentHashes) == 0 { workTree, err := repository.RootTree(ctx) if err != nil { return nil, err } - return workTree.Diff(ctx, hash.EmptyHash) + return workTree.Diff(ctx, hash.EmptyHash, pathPrefix) } else if len(repository.commit.ParentHashes) == 1 { - return repository.DiffCommit(ctx, repository.commit.ParentHashes[0]) + return repository.DiffCommit(ctx, repository.commit.ParentHashes[0], pathPrefix) } - return repository.DiffCommit(ctx, repository.commit.ParentHashes[1]) + return repository.DiffCommit(ctx, repository.commit.ParentHashes[1], pathPrefix) } // Merge implement merge like git, docs https://en.wikipedia.org/wiki/Merge_(version_control) @@ -555,12 +629,12 @@ func merge(ctx context.Context, return nil, err } - baseDiff, err := ancestorWorkTree.Diff(ctx, baseCommit.TreeHash) + baseDiff, err := ancestorWorkTree.Diff(ctx, baseCommit.TreeHash, "") if err != nil { return nil, err } - mergeDiff, err := ancestorWorkTree.Diff(ctx, toMergeCommit.TreeHash) + mergeDiff, err := ancestorWorkTree.Diff(ctx, toMergeCommit.TreeHash, "") if err != nil { return nil, err } diff --git a/versionmgr/work_repo_diff_test.go b/versionmgr/work_repo_diff_test.go index 693fd6ab..ed994342 100644 --- a/versionmgr/work_repo_diff_test.go +++ b/versionmgr/work_repo_diff_test.go @@ -74,7 +74,7 @@ func TestWorkRepositoryDiffCommit(t *testing.T) { require.NoError(t, rmWip(ctx, repo.WipRepo(), secondWip.ID)) require.NoError(t, workRepo.CheckOut(ctx, InCommit, baseCommit.Hash.Hex())) - changes, err := workRepo.DiffCommit(ctx, secondCommit.Hash) + changes, err := workRepo.DiffCommit(ctx, secondCommit.Hash, "") require.NoError(t, err) require.Equal(t, 4, changes.Num()) require.Equal(t, "a.txt", changes.Index(0).Path()) diff --git a/versionmgr/worktree.go b/versionmgr/worktree.go index 8e61e3ee..39370806 100644 --- a/versionmgr/worktree.go +++ b/versionmgr/worktree.go @@ -514,7 +514,7 @@ func (workTree *WorkTree) ApplyOneChange(ctx context.Context, change IChange) er return fmt.Errorf("unexpect change action: %s", action) } -func (workTree *WorkTree) Diff(ctx context.Context, rootTreeHash hash.Hash) (*Changes, error) { +func (workTree *WorkTree) Diff(ctx context.Context, rootTreeHash hash.Hash, prefix string) (*Changes, error) { toNode, err := NewTreeNode(ctx, models.NewRootTreeEntry(rootTreeHash), workTree.object) if err != nil { return nil, err @@ -527,7 +527,17 @@ func (workTree *WorkTree) Diff(ctx context.Context, rootTreeHash hash.Hash) (*Ch if err != nil { return nil, err } - return newChanges(changes), nil + + prefix = CleanPath(prefix) + var filteredChange []merkletrie.Change + for _, change := range changes { + path := change.Path() + if !strings.HasPrefix(path, prefix) { + continue + } + filteredChange = append(filteredChange, change) + } + return newChanges(filteredChange), nil } // CleanPath clean path From 103e9be577122422b04745828a60459f30352cc4 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Mon, 1 Jan 2024 22:52:38 +0800 Subject: [PATCH 134/210] feat: add api for revert wip changes --- api/jiaozifs.gen.go | 6 +- api/swagger.yml | 2 + integrationtest/helper_test.go | 10 ++ integrationtest/wip_object_test.go | 76 ++++++++- integrationtest/wip_test.go | 10 -- versionmgr/work_repo.go | 154 +++++++++++++++---- versionmgr/work_repo_diff_test.go | 28 +--- versionmgr/work_repo_merge_test.go | 105 +++++-------- versionmgr/work_repo_test.go | 238 +++++++++++++++++++++++------ 9 files changed, 445 insertions(+), 184 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 573e8956..5dfe1751 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -6924,9 +6924,9 @@ var swaggerSpec = []string{ "KqR7+TtRDzW8QykcJH/1ErpBbFxvzTtNrG0Vi7dOEoadHmwIP10Wlx9R9uHSR5IOPJN8BOr99lUTufhG", "kt9tHc4PEPfRvgratrGzfUhTddtA+b3tZ3ft5DEAozNE1Xwye1AvDthTnfLb1S7SEjHfm4rFjo3PriN3", "JrSHY0kwf4VDhaVfw7HwvR9cF2sHXcMya3LYt2Ttci9j4b3WE9uv/nXGUjtwWgYB70cjiM1Rdw/ilCVJ", - "awFKypm+vaNUrhHVfiOgy+EW+EDQ/Tfw0tp+jk4eIBahNR7P+fo/9NOJ6BdaCDUHb6PYyghxH2IrTUel", - "/Kpt5T6CJJUrzc78w/S6F47jNuLV494v1c9ofbpR0qp+0ss8qX2269ON4rrBYdcWWTn+MVBNw5SZ76GV", - "38g6GY9jFuB4wYQ8OX7x98PjMU7J+PbQUYO0dsCi6839/wcAAP//cXcyUE9sAAA=", + "awFKypm+vaNUrhHVfiOgy+EW+EDQ/Tfw0tp+jk4eIBahNR7P+fo/9NOJ6BdaCDUHb6PYygjxq0GgYzqb", + "SCoSS66MkqW6UqzVxgQfQZLKlWZ+/hl73QvHcRsf61Hyl+pHtz7dKNlWPwBmntQ+8vXpRsnIoLZrQ60c", + "Fhlgp2HKzNfTyi9qnYzHMQtwvGBCnhy/+Pvh8RinZHx76KhYWjtg0fXm/v8DAAD//8JWX0V9bAAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 0ede14b9..86c45c38 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -1070,6 +1070,8 @@ paths: description: Unauthorized 403: description: Forbidden + 500: + description: Server Internal Error /wip/{owner}/{repository}/changes: parameters: diff --git a/integrationtest/helper_test.go b/integrationtest/helper_test.go index d4d059b3..6b099f68 100644 --- a/integrationtest/helper_test.go +++ b/integrationtest/helper_test.go @@ -184,6 +184,16 @@ func deleteObject(ctx context.Context, c convey.C, client *api.Client, title str }) } +func createWip(ctx context.Context, c convey.C, client *api.Client, title string, user string, repoName string, refName string) { + c.Convey("create wip "+title, func() { + resp, err := client.GetWip(ctx, user, repoName, &api.GetWipParams{ + RefName: refName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + }) +} + func commitWip(ctx context.Context, c convey.C, client *api.Client, title string, user string, repoName string, refName string, msg string) { c.Convey("commit wip "+title, func() { resp, err := client.CommitWip(ctx, user, repoName, &api.CommitWipParams{ diff --git a/integrationtest/wip_object_test.go b/integrationtest/wip_object_test.go index 9c60f63d..d043283a 100644 --- a/integrationtest/wip_object_test.go +++ b/integrationtest/wip_object_test.go @@ -312,7 +312,6 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("get wip changes", func(c convey.C) { - c.Convey("no auth", func() { re := client.RequestEditors client.RequestEditors = nil @@ -381,5 +380,80 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(*result.JSON200, convey.ShouldHaveLength, 4) }) }) + + c.Convey("revert wip changes", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.RevertWipChanges(ctx, userName, repoName, &api.RevertWipChangesParams{ + RefName: branchName, + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("fail to revert changes in non exit user", func() { + resp, err := client.RevertWipChanges(ctx, "mockUser", repoName, &api.RevertWipChangesParams{ + RefName: branchName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to revert changes in non exit repo", func() { + resp, err := client.RevertWipChanges(ctx, userName, "fakeRepo", &api.RevertWipChangesParams{ + RefName: branchName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to revert changes in non exit branch", func() { + resp, err := client.RevertWipChanges(ctx, userName, repoName, &api.RevertWipChangesParams{ + RefName: "mockref", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("forbidden revert changes in others", func() { + resp, err := client.RevertWipChanges(ctx, "jimmy", "happygo", &api.RevertWipChangesParams{ + RefName: branchName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + + c.Convey("not exit path", func() { + resp, err := client.RevertWipChanges(ctx, userName, repoName, &api.RevertWipChangesParams{ + RefName: branchName, + PathPrefix: utils.String("a/b/c/d"), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("success to revert changes", func() { + resp, err := client.RevertWipChanges(ctx, userName, repoName, &api.RevertWipChangesParams{ + RefName: branchName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + { + resp, err = client.GetWipChanges(ctx, userName, repoName, &api.GetWipChangesParams{ + RefName: branchName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseGetWipChangesResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(*result.JSON200, convey.ShouldHaveLength, 0) + } + }) + }) + } } diff --git a/integrationtest/wip_test.go b/integrationtest/wip_test.go index 8f77de44..384423d7 100644 --- a/integrationtest/wip_test.go +++ b/integrationtest/wip_test.go @@ -189,13 +189,3 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { }) } } - -func createWip(ctx context.Context, c convey.C, client *api.Client, title string, user string, repoName string, refName string) { - c.Convey("create wip "+title, func() { - resp, err := client.GetWip(ctx, user, repoName, &api.GetWipParams{ - RefName: refName, - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) - }) -} diff --git a/versionmgr/work_repo.go b/versionmgr/work_repo.go index 58602fa6..420c6149 100644 --- a/versionmgr/work_repo.go +++ b/versionmgr/work_repo.go @@ -140,9 +140,13 @@ func (repository *WorkRepository) ReadBlob(ctx context.Context, blob *models.Blo // RootTree return worktree at root func (repository *WorkRepository) RootTree(ctx context.Context) (*WorkTree, error) { + return repository.rootTree(ctx, repository.repo) +} + +func (repository *WorkRepository) rootTree(ctx context.Context, repo models.IRepo) (*WorkTree, error) { if repository.headTree == nil { //use repo default headTree - ref, err := repository.repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.repoModel.ID).SetName(repository.repoModel.HEAD)) + ref, err := repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.repoModel.ID).SetName(repository.repoModel.HEAD)) if err != nil { return nil, err } @@ -151,7 +155,7 @@ func (repository *WorkRepository) RootTree(ctx context.Context) (*WorkTree, erro treeHash := hash.EmptyHash var commit *models.Commit if !ref.CommitHash.IsEmpty() { - commit, err = repository.repo.CommitRepo(repository.repoModel.ID).Commit(ctx, ref.CommitHash) + commit, err = repo.CommitRepo(repository.repoModel.ID).Commit(ctx, ref.CommitHash) if err != nil { return nil, err } @@ -160,7 +164,7 @@ func (repository *WorkRepository) RootTree(ctx context.Context) (*WorkTree, erro repository.setCurState(InBranch, nil, ref, commit) repository.headTree = &treeHash } - return NewWorkTree(ctx, repository.repo.FileTreeRepo(repository.repoModel.ID), models.NewRootTreeEntry(*repository.headTree)) + return NewWorkTree(ctx, repo.FileTreeRepo(repository.repoModel.ID), models.NewRootTreeEntry(*repository.headTree)) } func (repository *WorkRepository) CheckOut(ctx context.Context, refType WorkRepoState, refName string) error { @@ -211,6 +215,7 @@ func (repository *WorkRepository) CheckOut(ctx context.Context, refType WorkRepo return nil } +// Revert changes in wip, not a good algo, but maybe enough func (repository *WorkRepository) Revert(ctx context.Context, prefixPath string) error { if repository.state != InWip { return fmt.Errorf("working repo not in wip state") @@ -227,7 +232,7 @@ func (repository *WorkRepository) Revert(ctx context.Context, prefixPath string) prefixPath = CleanPath(prefixPath) if len(prefixPath) == 0 { - //just revert all + //just revert all, in fact this strategy can apply to all path , but not a easy work err := repository.repo.WipRepo().UpdateByID(ctx, models.NewUpdateWipParams(repository.wip.ID).SetCurrentTree(baseTreeHash)) if err != nil { return err @@ -246,7 +251,7 @@ func (repository *WorkRepository) Revert(ctx context.Context, prefixPath string) return err } - changes, err := curTree.Diff(ctx, baseTreeHash, prefixPath) + changes, err := baseTree.Diff(ctx, repository.wip.CurrentTree, prefixPath) if err != nil { return err } @@ -262,7 +267,7 @@ func (repository *WorkRepository) Revert(ctx context.Context, prefixPath string) changePath := change.Path() switch action { case merkletrie.Insert: - err = curTree.RemoveEntry(ctx, prefixPath) + err = curTree.RemoveEntry(ctx, changePath) if err != nil { return err } @@ -272,7 +277,7 @@ func (repository *WorkRepository) Revert(ctx context.Context, prefixPath string) return err } - err = curTree.ReplaceLeaf(ctx, prefixPath, blob) + err = curTree.ReplaceLeaf(ctx, changePath, blob) if err != nil { return err } @@ -282,7 +287,7 @@ func (repository *WorkRepository) Revert(ctx context.Context, prefixPath string) return err } - err = curTree.AddLeaf(ctx, prefixPath, blob) + err = curTree.AddLeaf(ctx, changePath, blob) if err != nil { return err } @@ -323,21 +328,118 @@ func (repository *WorkRepository) DeleteWip(ctx context.Context) error { // CommitChanges append a new commit to current headTree, read changes from wip, than create a new commit with parent point to current headTree, // and replace tree hash with wip's currentTreeHash. func (repository *WorkRepository) CommitChanges(ctx context.Context, msg string) (*models.Commit, error) { - if !(repository.state == InWip || repository.state == InBranch) { + if !(repository.state == InWip) { return nil, errors.New("must commit changes on branch") } - head := repository.headTree + if !bytes.Equal(repository.branch.CommitHash, repository.wip.BaseCommit) { + return nil, fmt.Errorf("base commit not equal with branch, please update wip") + } + creator, err := repository.repo.UserRepo().Get(ctx, models.NewGetUserParams().SetID(repository.wip.CreatorID)) if err != nil { return nil, err } + author := models.Signature{ + Name: creator.Name, + Email: creator.Email, + When: repository.wip.UpdatedAt, + } + + var commit *models.Commit + err = repository.repo.Transaction(ctx, func(repo models.IRepo) error { + var err error + commit, err = repository.commitChangeRoot(ctx, repo, author, repository.wip.CurrentTree, msg) + if err != nil { + return err + } + + return repo.WipRepo().UpdateByID(ctx, models.NewUpdateWipParams(repository.wip.ID).SetBaseCommit(commit.Hash)) + }) + if err != nil { + return nil, err + } + + repository.branch.CommitHash = commit.Hash + repository.wip.BaseCommit = commit.Hash + repository.headTree = &repository.wip.CurrentTree + return commit, err +} + +// ChangeInWip apply change to wip +func (repository *WorkRepository) ChangeInWip(ctx context.Context, changFn func(root *WorkTree) error) error { + return repository.repo.Transaction(ctx, func(repo models.IRepo) error { + workTree, err := repository.changeInWip(ctx, repo, changFn) + if err != nil { + return err + } + + repository.wip.CurrentTree = workTree.Root().Hash() + repository.headTree = &repository.wip.CurrentTree + return repo.WipRepo().UpdateByID(ctx, models.NewUpdateWipParams(repository.wip.ID).SetCurrentTree(workTree.Root().Hash())) + }) +} + +// ChangeAndCommit apply changes to tree, and create a new commit +func (repository *WorkRepository) ChangeAndCommit(ctx context.Context, msg string, changFn func(root *WorkTree) error) (*models.Commit, error) { if !bytes.Equal(repository.branch.CommitHash, repository.wip.BaseCommit) { return nil, fmt.Errorf("base commit not equal with branch, please update wip") } - parentHash := []hash.Hash{} + creator, err := repository.repo.UserRepo().Get(ctx, models.NewGetUserParams().SetID(repository.wip.CreatorID)) + if err != nil { + return nil, err + } + + author := models.Signature{ + Name: creator.Name, + Email: creator.Email, + When: repository.wip.UpdatedAt, + } + + var commit *models.Commit + err = repository.repo.Transaction(ctx, func(repo models.IRepo) error { + workTree, err := repository.changeInWip(ctx, repo, changFn) + if err != nil { + return err + } + commit, err = repository.commitChangeRoot(ctx, repo, author, workTree.Root().Hash(), msg) + if err != nil { + return err + } + + err = repo.WipRepo().UpdateByID(ctx, models.NewUpdateWipParams(repository.wip.ID).SetCurrentTree(workTree.Root().Hash()).SetBaseCommit(commit.Hash)) + if err != nil { + return err + } + repository.wip.CurrentTree = workTree.Root().Hash() + repository.headTree = &repository.wip.CurrentTree + return err + }) + if err != nil { + return nil, err + } + repository.branch.CommitHash = commit.Hash + repository.wip.BaseCommit = commit.Hash + return commit, err +} + +func (repository *WorkRepository) changeInWip(ctx context.Context, repo models.IRepo, changFn func(root *WorkTree) error) (*WorkTree, error) { + if !(repository.state == InWip) { + return nil, errors.New("must commit changes on branch") + } + + workTree, err := repository.rootTree(ctx, repo) + if err != nil { + return nil, err + } + + return workTree, changFn(workTree) +} + +func (repository *WorkRepository) commitChangeRoot(ctx context.Context, repo models.IRepo, author models.Signature, root hash.Hash, msg string) (*models.Commit, error) { + parentHash := make([]hash.Hash, 0) //avoid nil parent if !repository.branch.CommitHash.IsEmpty() { parentHash = []hash.Hash{repository.branch.CommitHash} } @@ -345,11 +447,7 @@ func (repository *WorkRepository) CommitChanges(ctx context.Context, msg string) commit := &models.Commit{ Hash: nil, RepositoryID: repository.repoModel.ID, - Author: models.Signature{ - Name: creator.Name, - Email: creator.Email, - When: repository.wip.UpdatedAt, - }, + Author: author, Committer: models.Signature{ Name: repository.operator.Name, Email: repository.operator.Email, @@ -357,7 +455,7 @@ func (repository *WorkRepository) CommitChanges(ctx context.Context, msg string) }, MergeTag: "", Message: msg, - TreeHash: repository.wip.CurrentTree, + TreeHash: root, ParentHashes: parentHash, CreatedAt: time.Now(), UpdatedAt: time.Now(), @@ -367,27 +465,17 @@ func (repository *WorkRepository) CommitChanges(ctx context.Context, msg string) return nil, err } commit.Hash = commitHash - err = repository.repo.Transaction(ctx, func(repo models.IRepo) error { - _, err = repo.CommitRepo(repository.repoModel.ID).Insert(ctx, commit) - if err != nil { - return err - } - - err = repo.WipRepo().UpdateByID(ctx, models.NewUpdateWipParams(repository.wip.ID).SetBaseCommit(commitHash)) - if err != nil { - return err - } - head = &repository.wip.CurrentTree - return repo.BranchRepo().UpdateByID(ctx, models.NewUpdateBranchParams(repository.branch.ID).SetCommitHash(commitHash)) - }) + _, err = repo.CommitRepo(repository.repoModel.ID).Insert(ctx, commit) if err != nil { return nil, err } - repository.branch.CommitHash = commitHash - repository.wip.BaseCommit = commitHash - repository.headTree = head + // Update branch + err = repo.BranchRepo().UpdateByID(ctx, models.NewUpdateBranchParams(repository.branch.ID).SetCommitHash(commitHash)) + if err != nil { + return nil, err + } return commit, err } diff --git a/versionmgr/work_repo_diff_test.go b/versionmgr/work_repo_diff_test.go index ed994342..3705f703 100644 --- a/versionmgr/work_repo_diff_test.go +++ b/versionmgr/work_repo_diff_test.go @@ -23,7 +23,7 @@ func TestWorkRepositoryDiffCommit(t *testing.T) { user, err := makeUser(ctx, repo.UserRepo(), "admin") require.NoError(t, err) - project, err := makeRepository(ctx, repo.RepositoryRepo(), user, "testproject") + project, err := makeRepository(ctx, repo, user, "testproject") require.NoError(t, err) //commit1 a.txt b/c.txt b/e.txt //commit2 a.txt b/d.txt b/e.txt @@ -34,25 +34,15 @@ func TestWorkRepositoryDiffCommit(t *testing.T) { ` workRepo := NewWorkRepositoryFromAdapter(ctx, user, project, repo, adapter) - //base branch + //base branch err = workRepo.CheckOut(ctx, InCommit, hash.EmptyHash.Hex()) require.NoError(t, err) - baseBranch, err := workRepo.CreateBranch(ctx, "feat/base") + _, err = workRepo.CreateBranch(ctx, "feat/base") require.NoError(t, err) - root1, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), EmptyDirEntry, testData1) + baseCommit, err := addChangesToWip(ctx, workRepo, "feat/base", "base commit", testData1) require.NoError(t, err) - baseWip, err := makeWip(ctx, repo.WipRepo(), user.ID, project.ID, baseBranch.ID, EmptyRoot.Hash, root1.Hash) - require.NoError(t, err) - - _, err = workRepo.CommitChanges(ctx, "base commit") //asset not correct state - require.Error(t, err) - require.NoError(t, workRepo.CheckOut(ctx, InWip, "feat/base")) - - baseCommit, err := workRepo.CommitChanges(ctx, "base commit") - require.NoError(t, err) - require.NoError(t, rmWip(ctx, repo.WipRepo(), baseWip.ID)) testData2 := ` 3|a.txt |a1 @@ -60,18 +50,14 @@ func TestWorkRepositoryDiffCommit(t *testing.T) { 3|b/e.txt |e2 1|b/g.txt |g1 ` - diffBranch, err := makeBranch(ctx, repo.BranchRepo(), user, "feat/diff", project.ID, hash.EmptyHash) - require.NoError(t, err) - root2, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(root1.Hash), testData2) + err = workRepo.CheckOut(ctx, InBranch, "feat/base") require.NoError(t, err) - secondWip, err := makeWip(ctx, repo.WipRepo(), user.ID, project.ID, diffBranch.ID, EmptyRoot.Hash, root2.Hash) + _, err = workRepo.CreateBranch(ctx, "feat/diff") require.NoError(t, err) - require.NoError(t, workRepo.CheckOut(ctx, InWip, "feat/diff")) - secondCommit, err := workRepo.CommitChanges(ctx, "merge commit") + secondCommit, err := addChangesToWip(ctx, workRepo, "feat/diff", "merge commit", testData2) require.NoError(t, err) - require.NoError(t, rmWip(ctx, repo.WipRepo(), secondWip.ID)) require.NoError(t, workRepo.CheckOut(ctx, InCommit, baseCommit.Hash.Hex())) changes, err := workRepo.DiffCommit(ctx, secondCommit.Hash, "") diff --git a/versionmgr/work_repo_merge_test.go b/versionmgr/work_repo_merge_test.go index 18526386..76c102f8 100644 --- a/versionmgr/work_repo_merge_test.go +++ b/versionmgr/work_repo_merge_test.go @@ -34,65 +34,50 @@ func TestCommitOpMerge(t *testing.T) { user, err := makeUser(ctx, repo.UserRepo(), "admin") require.NoError(t, err) - project, err := makeRepository(ctx, repo.RepositoryRepo(), user, "testproject") + project, err := makeRepository(ctx, repo, user, "testproject") require.NoError(t, err) testData := ` 1|a.txt |h1 1|b/c.txt |h2 ` - oriRoot, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), EmptyDirEntry, testData) + workRepo := NewWorkRepositoryFromAdapter(ctx, user, project, repo, adapter) + + err = workRepo.CheckOut(ctx, InCommit, hash.EmptyHash.Hex()) require.NoError(t, err) - //base branch - baseBranch, err := makeBranch(ctx, repo.BranchRepo(), user, "feat/base", project.ID, hash.EmptyHash) + _, err = workRepo.CreateBranch(ctx, "feat/base") require.NoError(t, err) - oriWip, err := makeWip(ctx, repo.WipRepo(), user.ID, project.ID, baseBranch.ID, hash.Hash{}, oriRoot.Hash) + oriCommit, err := addChangesToWip(ctx, workRepo, "feat/base", "base commit", testData) require.NoError(t, err) - workRepo := NewWorkRepositoryFromAdapter(ctx, user, project, repo, adapter) - - require.NoError(t, workRepo.CheckOut(ctx, InWip, "feat/base")) - oriCommit, err := workRepo.CommitChanges(ctx, "") - require.NoError(t, err) - require.NoError(t, rmWip(ctx, repo.WipRepo(), oriWip.ID)) //modify a.txt //---------------CommitA testData = ` 3|a.txt |h5 3|b/c.txt |h2 ` - branchA, err := makeBranch(ctx, repo.BranchRepo(), user, "feat/branchA", project.ID, oriCommit.Hash) + err = workRepo.CheckOut(ctx, InCommit, oriCommit.Hash.Hex()) require.NoError(t, err) - - baseModify, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(oriCommit.TreeHash), testData) + _, err = workRepo.CreateBranch(ctx, "feat/branchA") require.NoError(t, err) - baseWip, err := makeWip(ctx, repo.WipRepo(), user.ID, project.ID, branchA.ID, oriCommit.Hash, baseModify.Hash) + commitA, err := addChangesToWip(ctx, workRepo, "feat/branchA", "commit a", testData) require.NoError(t, err) - require.NoError(t, workRepo.CheckOut(ctx, InWip, "feat/branchA")) - commitA, err := workRepo.CommitChanges(ctx, "commit a") - require.NoError(t, err) - require.NoError(t, rmWip(ctx, repo.WipRepo(), baseWip.ID)) - //modify a.txt //---------------CommitB testData = ` 3|a.txt |h4 3|b/c.txt |h2 ` - branchB, err := makeBranch(ctx, repo.BranchRepo(), user, "feat/branchB", project.ID, oriCommit.Hash) - require.NoError(t, err) - mergeModify, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(oriCommit.TreeHash), testData) + err = workRepo.CheckOut(ctx, InCommit, oriCommit.Hash.Hex()) require.NoError(t, err) - mergeWip, err := makeWip(ctx, repo.WipRepo(), user.ID, project.ID, branchB.ID, oriCommit.Hash, mergeModify.Hash) + _, err = workRepo.CreateBranch(ctx, "feat/branchB") require.NoError(t, err) - require.NoError(t, workRepo.CheckOut(ctx, InWip, "feat/branchB")) - commitB, err := workRepo.CommitChanges(ctx, "commit b") + commitB, err := addChangesToWip(ctx, workRepo, "feat/branchB", "commit b", testData) require.NoError(t, err) - require.NoError(t, rmWip(ctx, repo.WipRepo(), mergeWip.ID)) //--------------CommitAB require.NoError(t, workRepo.CheckOut(ctx, InBranch, "feat/branchA")) @@ -103,16 +88,13 @@ func TestCommitOpMerge(t *testing.T) { testData = ` 1|x.txt |h4 ` - branchF, err := makeBranch(ctx, repo.BranchRepo(), user, "feat/branchF", project.ID, commitAB.Hash) + err = workRepo.CheckOut(ctx, InCommit, commitAB.Hash.Hex()) require.NoError(t, err) - rootF, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(commitAB.TreeHash), testData) + _, err = workRepo.CreateBranch(ctx, "feat/branchF") require.NoError(t, err) - mergeWipF, err := makeWip(ctx, repo.WipRepo(), user.ID, project.ID, branchF.ID, commitAB.Hash, rootF.Hash) - require.NoError(t, err) - require.NoError(t, workRepo.CheckOut(ctx, InWip, "feat/branchF")) - _, err = workRepo.CommitChanges(ctx, "commit f") + + _, err = addChangesToWip(ctx, workRepo, "feat/branchF", "commit f", testData) require.NoError(t, err) - require.NoError(t, rmWip(ctx, repo.WipRepo(), mergeWipF.ID)) //commitC require.NoError(t, workRepo.CheckOut(ctx, InBranch, "feat/branchF")) @@ -125,29 +107,20 @@ func TestCommitOpMerge(t *testing.T) { 3|b/c.txt |h6 1|g/c.txt |h7 ` - branchDE, err := makeBranch(ctx, repo.BranchRepo(), user, "feat/branchD_E", project.ID, commitB.Hash) + err = workRepo.CheckOut(ctx, InCommit, commitB.Hash.Hex()) require.NoError(t, err) - modifyD, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(commitB.TreeHash), testData) + _, err = workRepo.CreateBranch(ctx, "feat/branchD_E") require.NoError(t, err) - mergeWipD, err := makeWip(ctx, repo.WipRepo(), user.ID, project.ID, branchDE.ID, commitB.Hash, modifyD.Hash) - require.NoError(t, err) - require.NoError(t, workRepo.CheckOut(ctx, InWip, "feat/branchD_E")) - commitD, err := workRepo.CommitChanges(ctx, "commit d") + + _, err = addChangesToWip(ctx, workRepo, "feat/branchD_E", "commit d", testData) require.NoError(t, err) - require.NoError(t, rmWip(ctx, repo.WipRepo(), mergeWipD.ID)) //commitE testData = ` 2|a.txt |h4 ` - modifyE, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(commitD.TreeHash), testData) - require.NoError(t, err) - mergeWipE, err := makeWip(ctx, repo.WipRepo(), user.ID, project.ID, branchDE.ID, commitD.Hash, modifyE.Hash) + commitE, err := addChangesToWip(ctx, workRepo, "feat/branchD_E", "commit e", testData) require.NoError(t, err) - //require.NoError(t, workRepo.CheckOut(ctx, InWip, "feat/branchD_E")) - commitE, err := workRepo.CommitChanges(ctx, "commit e") - require.NoError(t, err) - require.NoError(t, rmWip(ctx, repo.WipRepo(), mergeWipE.ID)) //test fast-ward require.NoError(t, workRepo.CheckOut(ctx, InBranch, "feat/branchB")) @@ -190,7 +163,7 @@ func TestCrissCrossMerge(t *testing.T) { user, err := makeUser(ctx, repo.UserRepo(), "admin") require.NoError(t, err) - project, err := makeRepository(ctx, repo.RepositoryRepo(), user, "testproject") + project, err := makeRepository(ctx, repo, user, "testproject") require.NoError(t, err) workRepo := NewWorkRepositoryFromAdapter(ctx, user, project, repo, adapter) @@ -198,51 +171,41 @@ func TestCrissCrossMerge(t *testing.T) { 1|a.txt |h1 1|b.txt |h2 ` - oriRoot, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), EmptyDirEntry, testData) + + err = workRepo.CheckOut(ctx, InCommit, hash.EmptyHash.Hex()) require.NoError(t, err) - //base branch - baseBranch, err := makeBranch(ctx, repo.BranchRepo(), user, "feat/base", project.ID, hash.EmptyHash) + _, err = workRepo.CreateBranch(ctx, "feat/base") require.NoError(t, err) - oriWip, err := makeWip(ctx, repo.WipRepo(), user.ID, project.ID, baseBranch.ID, hash.Hash{}, oriRoot.Hash) + oriCommit, err := addChangesToWip(ctx, workRepo, "feat/base", "base commit", testData) require.NoError(t, err) - require.NoError(t, workRepo.CheckOut(ctx, InWip, "feat/base")) - oriCommit, err := workRepo.CommitChanges(ctx, "base commit") - require.NoError(t, err) - require.NoError(t, rmWip(ctx, repo.WipRepo(), oriWip.ID)) //------------------CommitC testData = ` 3|a.txt |h1 3|b.txt |h3 ` - branchC, err := makeBranch(ctx, repo.BranchRepo(), user, "feat/branchC", project.ID, oriCommit.Hash) - require.NoError(t, err) - baseModify, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(oriCommit.TreeHash), testData) + err = workRepo.CheckOut(ctx, InCommit, oriCommit.Hash.Hex()) require.NoError(t, err) - wipC, err := makeWip(ctx, repo.WipRepo(), user.ID, project.ID, branchC.ID, oriCommit.Hash, baseModify.Hash) + _, err = workRepo.CreateBranch(ctx, "feat/branchC") require.NoError(t, err) - require.NoError(t, workRepo.CheckOut(ctx, InWip, "feat/branchC")) - commitC, err := workRepo.CommitChanges(ctx, "commit c") + commitC, err := addChangesToWip(ctx, workRepo, "feat/branchC", "base commit", testData) require.NoError(t, err) - require.NoError(t, rmWip(ctx, repo.WipRepo(), wipC.ID)) + //modify a.txt //-----------------CommitB testData = ` 3|a.txt |h4 3|b.txt |h2 ` - branchB, err := makeBranch(ctx, repo.BranchRepo(), user, "feat/branchB", project.ID, oriCommit.Hash) + err = workRepo.CheckOut(ctx, InCommit, oriCommit.Hash.Hex()) require.NoError(t, err) - mergeModify, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), models.NewRootTreeEntry(oriCommit.TreeHash), testData) + _, err = workRepo.CreateBranch(ctx, "feat/branchB") require.NoError(t, err) - wipB, err := makeWip(ctx, repo.WipRepo(), user.ID, project.ID, branchB.ID, oriCommit.Hash, mergeModify.Hash) - require.NoError(t, err) - require.NoError(t, workRepo.CheckOut(ctx, InWip, "feat/branchB")) - commitB, err := workRepo.CommitChanges(ctx, "commit b") + + commitB, err := addChangesToWip(ctx, workRepo, "feat/branchB", "base commit", testData) require.NoError(t, err) - require.NoError(t, rmWip(ctx, repo.WipRepo(), wipB.ID)) //-----------------CommitAB require.NoError(t, workRepo.CheckOut(ctx, InBranch, "feat/branchB")) diff --git a/versionmgr/work_repo_test.go b/versionmgr/work_repo_test.go index 5194f94c..d2d32e32 100644 --- a/versionmgr/work_repo_test.go +++ b/versionmgr/work_repo_test.go @@ -171,7 +171,7 @@ func TestNewWorkRepositoryFromConfig(t *testing.T) { }) } -func TestWorkRepository_RootTree(t *testing.T) { +func TestWorkRepositoryRootTree(t *testing.T) { ctx := context.Background() postgres, _, db := testhelper.SetupDatabase(ctx, t) @@ -181,10 +181,7 @@ func TestWorkRepository_RootTree(t *testing.T) { user, err := makeUser(ctx, repo.UserRepo(), "admin") require.NoError(t, err) - project, err := makeRepository(ctx, repo.RepositoryRepo(), user, "testproject") - require.NoError(t, err) - - mainBranch, err := makeBranch(ctx, repo.BranchRepo(), user, "main", project.ID, hash.EmptyHash) + project, err := makeRepository(ctx, repo, user, "testproject") require.NoError(t, err) workRepo := NewWorkRepositoryFromAdapter(ctx, user, project, repo, mem.New(ctx)) @@ -195,18 +192,10 @@ func TestWorkRepository_RootTree(t *testing.T) { 1|a.txt |h1 1|b/c.txt |h2 ` - oriRoot, err := makeRoot(ctx, repo.FileTreeRepo(project.ID), EmptyDirEntry, testData) - require.NoError(t, err) - mainWip, err := makeWip(ctx, repo.WipRepo(), user.ID, project.ID, mainBranch.ID, hash.Hash{}, oriRoot.Hash) - require.NoError(t, err) - require.Error(t, workRepo.CheckOut(ctx, WorkRepoState("mock"), "main")) - - require.NoError(t, workRepo.CheckOut(ctx, InWip, "main")) - require.Equal(t, mainWip.ID, workRepo.CurWip().ID) - require.Equal(t, "main", workRepo.CurBranch().Name) - commit, err := workRepo.CommitChanges(ctx, "init commit") + commit, err := addChangesToWip(ctx, workRepo, "main", "init commit", testData) require.NoError(t, err) + require.Error(t, workRepo.CheckOut(ctx, WorkRepoState("mock"), "main")) workTree, err := workRepo.RootTree(ctx) require.NoError(t, err) @@ -219,6 +208,151 @@ func TestWorkRepository_RootTree(t *testing.T) { require.Equal(t, "main", workRepo.CurBranch().Name) } +func TestWorkRepositoryRevert(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + + repo := models.NewRepo(db) + adapter := mem.New(ctx) + + user, err := makeUser(ctx, repo.UserRepo(), "admin") + require.NoError(t, err) + + var checkFn = func(workRepo *WorkRepository, compareTree hash.Hash, path string, num int) { + beforeTree, err := workRepo.RootTree(ctx) + require.NoError(t, err) + beforeChanges, err := beforeTree.Diff(ctx, compareTree, path) + require.NoError(t, err) + require.Equal(t, num, beforeChanges.Num()) + + //revert + err = workRepo.Revert(ctx, path) + require.NoError(t, err) + + //after tree + afterTree, err := workRepo.RootTree(ctx) + require.NoError(t, err) + afterChanges, err := afterTree.Diff(ctx, compareTree, path) + require.NoError(t, err) + require.Equal(t, 0, afterChanges.Num()) + } + + t.Run("revert", func(t *testing.T) { + project, err := makeRepository(ctx, repo, user, "testRevert") + require.NoError(t, err) + testData1 := ` +1|a.txt |a +1|b/c.txt |c +1|b/e.txt |e1 +` + + workRepo := NewWorkRepositoryFromAdapter(ctx, user, project, repo, adapter) + + initCommit, err := addChangesToWip(ctx, workRepo, "main", "base commit", testData1) + require.NoError(t, err) + testData2 := ` +3|a.txt |a1 +2|b/c.txt |d +3|b/e.txt |e2 +1|b/g.txt |g1 +` + err = workRepo.CheckOut(ctx, InBranch, "main") + require.NoError(t, err) + err = workRepo.ChangeInWip(ctx, func(workTree *WorkTree) error { + return appendChangeToWorkTree(ctx, workTree, testData2) + }) + require.Error(t, err) //state not correct + + err = workRepo.CheckOut(ctx, InWip, "main") + require.NoError(t, err) + + err = workRepo.ChangeInWip(ctx, func(workTree *WorkTree) error { + return appendChangeToWorkTree(ctx, workTree, testData2) + }) + require.NoError(t, err) + + checkFn(workRepo, initCommit.TreeHash, "a.txt", 1) + checkFn(workRepo, initCommit.TreeHash, "b/c.txt", 1) + checkFn(workRepo, initCommit.TreeHash, "b/e.txt", 1) + checkFn(workRepo, initCommit.TreeHash, "b/g.txt", 1) + }) + + t.Run("revert dir", func(t *testing.T) { + project, err := makeRepository(ctx, repo, user, "testRevertDir") + require.NoError(t, err) + testData1 := ` +1|a.txt |a +1|b/c.txt |c +1|b/e.txt |e1 +` + + workRepo := NewWorkRepositoryFromAdapter(ctx, user, project, repo, adapter) + + initCommit, err := addChangesToWip(ctx, workRepo, "main", "base commit", testData1) + require.NoError(t, err) + testData2 := ` +3|a.txt |a1 +2|b/c.txt |d +3|b/e.txt |e2 +1|b/g.txt |g1 +` + err = workRepo.CheckOut(ctx, InBranch, "main") + require.NoError(t, err) + err = workRepo.ChangeInWip(ctx, func(workTree *WorkTree) error { + return appendChangeToWorkTree(ctx, workTree, testData2) + }) + require.Error(t, err) //state not correct + + err = workRepo.CheckOut(ctx, InWip, "main") + require.NoError(t, err) + + err = workRepo.ChangeInWip(ctx, func(workTree *WorkTree) error { + return appendChangeToWorkTree(ctx, workTree, testData2) + }) + require.NoError(t, err) + + checkFn(workRepo, initCommit.TreeHash, "b", 3) + }) + + t.Run("revert all", func(t *testing.T) { + project, err := makeRepository(ctx, repo, user, "testRevertAll") + require.NoError(t, err) + testData1 := ` +1|a.txt |a +1|b/c.txt |c +1|b/e.txt |e1 +` + + workRepo := NewWorkRepositoryFromAdapter(ctx, user, project, repo, adapter) + + initCommit, err := addChangesToWip(ctx, workRepo, "main", "base commit", testData1) + require.NoError(t, err) + testData2 := ` +3|a.txt |a1 +2|b/c.txt |d +3|b/e.txt |e2 +1|b/g.txt |g1 +` + err = workRepo.CheckOut(ctx, InBranch, "main") + require.NoError(t, err) + err = workRepo.ChangeInWip(ctx, func(workTree *WorkTree) error { + return appendChangeToWorkTree(ctx, workTree, testData2) + }) + require.Error(t, err) //state not correct + + err = workRepo.CheckOut(ctx, InWip, "main") + require.NoError(t, err) + + err = workRepo.ChangeInWip(ctx, func(workTree *WorkTree) error { + return appendChangeToWorkTree(ctx, workTree, testData2) + }) + require.NoError(t, err) + + checkFn(workRepo, initCommit.TreeHash, "", 4) + }) +} + func makeUser(ctx context.Context, userRepo models.IUserRepo, name string) (*models.User, error) { user := &models.User{ Name: name, @@ -234,8 +368,8 @@ func makeUser(ctx context.Context, userRepo models.IUserRepo, name string) (*mod return userRepo.Insert(ctx, user) } -func makeRepository(ctx context.Context, repoRepo models.IRepositoryRepo, user *models.User, name string) (*models.Repository, error) { - return repoRepo.Insert(ctx, &models.Repository{ +func makeRepository(ctx context.Context, repo models.IRepo, user *models.User, name string) (*models.Repository, error) { + repoModel, err := repo.RepositoryRepo().Insert(ctx, &models.Repository{ Name: name, Description: utils.String("test"), HEAD: "main", @@ -244,6 +378,22 @@ func makeRepository(ctx context.Context, repoRepo models.IRepositoryRepo, user * CreatorID: user.ID, StorageNamespace: utils.String("mem://data"), }) + if err != nil { + return nil, err + } + _, err = repo.BranchRepo().Insert(ctx, &models.Branch{ + RepositoryID: repoModel.ID, + CommitHash: hash.EmptyHash, + Name: "main", + Description: nil, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + CreatorID: user.ID, + }) + if err != nil { + return nil, err + } + return repoModel, nil } // nolint @@ -289,30 +439,29 @@ func makeBranch(ctx context.Context, branchRepo models.IBranchRepo, user *models return branchRepo.Insert(ctx, branch) } -func makeWip(ctx context.Context, wipRepo models.IWipRepo, creatorID, repoID, branchID uuid.UUID, parentHash, curHash hash.Hash) (*models.WorkingInProcess, error) { - wip := &models.WorkingInProcess{ - CurrentTree: curHash, - BaseCommit: parentHash, - RefID: branchID, - RepositoryID: repoID, - CreatorID: creatorID, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), +func addChangesToWip(ctx context.Context, workRepo *WorkRepository, branchName string, msg string, testData string) (*models.Commit, error) { + err := workRepo.CheckOut(ctx, InBranch, branchName) + if err != nil { + return nil, err + } + _, _, err = workRepo.GetOrCreateWip(ctx) + if err != nil { + return nil, err } - return wipRepo.Insert(ctx, wip) -} - -func rmWip(ctx context.Context, wipRepo models.IWipRepo, wipID uuid.UUID) error { - _, err := wipRepo.Delete(ctx, models.NewDeleteWipParams().SetID(wipID)) - return err -} -func makeRoot(ctx context.Context, objRepo models.IFileTreeRepo, treeEntry models.TreeEntry, testData string) (*models.TreeNode, error) { - lines := strings.Split(testData, "\n") - treeOp, err := NewWorkTree(ctx, objRepo, treeEntry) + err = workRepo.CheckOut(ctx, InWip, branchName) if err != nil { return nil, err } + + return workRepo.ChangeAndCommit(ctx, msg, func(workTree *WorkTree) error { + return appendChangeToWorkTree(ctx, workTree, testData) + }) +} + +func appendChangeToWorkTree(ctx context.Context, workTree *WorkTree, testData string) error { + lines := strings.Split(testData, "\n") + var err error for _, line := range lines { if len(strings.TrimSpace(line)) == 0 { continue @@ -322,7 +471,7 @@ func makeRoot(ctx context.Context, objRepo models.IFileTreeRepo, treeEntry model fileHash := strings.TrimSpace(commitData[2]) blob := &models.Blob{ Hash: hash.Hash(fileHash), - RepositoryID: objRepo.RepositoryID(), + RepositoryID: workTree.RepositoryID(), Type: models.BlobObject, Size: 10, Properties: models.Property{Mode: filemode.Regular}, @@ -331,23 +480,22 @@ func makeRoot(ctx context.Context, objRepo models.IFileTreeRepo, treeEntry model } if commitData[0] == "1" { - err = treeOp.AddLeaf(ctx, fullPath, blob) + err = workTree.AddLeaf(ctx, fullPath, blob) if err != nil { - return nil, err + return err } } else if commitData[0] == "3" { - err = treeOp.ReplaceLeaf(ctx, fullPath, blob) + err = workTree.ReplaceLeaf(ctx, fullPath, blob) if err != nil { - return nil, err + return err } } else { //2 - err = treeOp.RemoveEntry(ctx, fullPath) + err = workTree.RemoveEntry(ctx, fullPath) if err != nil { - return nil, err + return err } } - } - return treeOp.Root().TreeNode(), nil + return nil } From c7cf5b0971da1cc551d05e334350f2cf5b840bce Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Sun, 7 Jan 2024 19:48:42 +0800 Subject: [PATCH 135/210] feat: support show objects from specific commit --- api/jiaozifs.gen.go | 203 +++++++++++++++++---------------- api/swagger.yml | 14 ++- controller/commit_ctl.go | 30 ++++- controller/repository_ctl.go | 2 +- integrationtest/commit_test.go | 66 ++++++++++- integrationtest/repo_test.go | 22 ++-- utils/hash/hash.go | 2 +- 7 files changed, 214 insertions(+), 125 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 5dfe1751..f2b42b0a 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -389,8 +389,8 @@ type GetCommitChangesParams struct { Path *string `form:"path,omitempty" json:"path,omitempty"` } -// GetCommitsInRepositoryParams defines parameters for GetCommitsInRepository. -type GetCommitsInRepositoryParams struct { +// GetCommitsInRefParams defines parameters for GetCommitsInRef. +type GetCommitsInRefParams struct { // After return items after this value After *PaginationCommitAfter `form:"after,omitempty" json:"after,omitempty"` @@ -412,10 +412,10 @@ type GetEntriesInRefParams struct { // Path specific path, if not specific return entries in root Path *string `form:"path,omitempty" json:"path,omitempty"` - // Ref specific branch, default to repostiory default branch(HEAD) + // Ref specific( ref name, tag name, commit hash), for wip and branchm, branch name default to repository default branch(HEAD), Ref *string `form:"ref,omitempty" json:"ref,omitempty"` - // Type type indicate to retrieve from wip/branch/tag, default branch + // Type type indicate to retrieve from wip/branch/tag/commit, default branch Type RefType `form:"type" json:"type"` } @@ -621,8 +621,8 @@ type ClientInterface interface { // GetCommitChanges request GetCommitChanges(ctx context.Context, owner string, repository string, commitId string, params *GetCommitChangesParams, reqEditors ...RequestEditorFn) (*http.Response, error) - // GetCommitsInRepository request - GetCommitsInRepository(ctx context.Context, owner string, repository string, params *GetCommitsInRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetCommitsInRef request + GetCommitsInRef(ctx context.Context, owner string, repository string, params *GetCommitsInRefParams, reqEditors ...RequestEditorFn) (*http.Response, error) // CompareCommit request CompareCommit(ctx context.Context, owner string, repository string, basehead string, params *CompareCommitParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -878,8 +878,8 @@ func (c *Client) GetCommitChanges(ctx context.Context, owner string, repository return c.Client.Do(req) } -func (c *Client) GetCommitsInRepository(ctx context.Context, owner string, repository string, params *GetCommitsInRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetCommitsInRepositoryRequest(c.Server, owner, repository, params) +func (c *Client) GetCommitsInRef(ctx context.Context, owner string, repository string, params *GetCommitsInRefParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetCommitsInRefRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -1974,8 +1974,8 @@ func NewGetCommitChangesRequest(server string, owner string, repository string, return req, nil } -// NewGetCommitsInRepositoryRequest generates requests for GetCommitsInRepository -func NewGetCommitsInRepositoryRequest(server string, owner string, repository string, params *GetCommitsInRepositoryParams) (*http.Request, error) { +// NewGetCommitsInRefRequest generates requests for GetCommitsInRef +func NewGetCommitsInRefRequest(server string, owner string, repository string, params *GetCommitsInRefParams) (*http.Request, error) { var err error var pathParam0 string @@ -3031,8 +3031,8 @@ type ClientWithResponsesInterface interface { // GetCommitChangesWithResponse request GetCommitChangesWithResponse(ctx context.Context, owner string, repository string, commitId string, params *GetCommitChangesParams, reqEditors ...RequestEditorFn) (*GetCommitChangesResponse, error) - // GetCommitsInRepositoryWithResponse request - GetCommitsInRepositoryWithResponse(ctx context.Context, owner string, repository string, params *GetCommitsInRepositoryParams, reqEditors ...RequestEditorFn) (*GetCommitsInRepositoryResponse, error) + // GetCommitsInRefWithResponse request + GetCommitsInRefWithResponse(ctx context.Context, owner string, repository string, params *GetCommitsInRefParams, reqEditors ...RequestEditorFn) (*GetCommitsInRefResponse, error) // CompareCommitWithResponse request CompareCommitWithResponse(ctx context.Context, owner string, repository string, basehead string, params *CompareCommitParams, reqEditors ...RequestEditorFn) (*CompareCommitResponse, error) @@ -3385,14 +3385,14 @@ func (r GetCommitChangesResponse) StatusCode() int { return 0 } -type GetCommitsInRepositoryResponse struct { +type GetCommitsInRefResponse struct { Body []byte HTTPResponse *http.Response JSON200 *[]Commit } // Status returns HTTPResponse.Status -func (r GetCommitsInRepositoryResponse) Status() string { +func (r GetCommitsInRefResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3400,7 +3400,7 @@ func (r GetCommitsInRepositoryResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetCommitsInRepositoryResponse) StatusCode() int { +func (r GetCommitsInRefResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -3884,13 +3884,13 @@ func (c *ClientWithResponses) GetCommitChangesWithResponse(ctx context.Context, return ParseGetCommitChangesResponse(rsp) } -// GetCommitsInRepositoryWithResponse request returning *GetCommitsInRepositoryResponse -func (c *ClientWithResponses) GetCommitsInRepositoryWithResponse(ctx context.Context, owner string, repository string, params *GetCommitsInRepositoryParams, reqEditors ...RequestEditorFn) (*GetCommitsInRepositoryResponse, error) { - rsp, err := c.GetCommitsInRepository(ctx, owner, repository, params, reqEditors...) +// GetCommitsInRefWithResponse request returning *GetCommitsInRefResponse +func (c *ClientWithResponses) GetCommitsInRefWithResponse(ctx context.Context, owner string, repository string, params *GetCommitsInRefParams, reqEditors ...RequestEditorFn) (*GetCommitsInRefResponse, error) { + rsp, err := c.GetCommitsInRef(ctx, owner, repository, params, reqEditors...) if err != nil { return nil, err } - return ParseGetCommitsInRepositoryResponse(rsp) + return ParseGetCommitsInRefResponse(rsp) } // CompareCommitWithResponse request returning *CompareCommitResponse @@ -4338,15 +4338,15 @@ func ParseGetCommitChangesResponse(rsp *http.Response) (*GetCommitChangesRespons return response, nil } -// ParseGetCommitsInRepositoryResponse parses an HTTP response from a GetCommitsInRepositoryWithResponse call -func ParseGetCommitsInRepositoryResponse(rsp *http.Response) (*GetCommitsInRepositoryResponse, error) { +// ParseGetCommitsInRefResponse parses an HTTP response from a GetCommitsInRefWithResponse call +func ParseGetCommitsInRefResponse(rsp *http.Response) (*GetCommitsInRefResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetCommitsInRepositoryResponse{ + response := &GetCommitsInRefResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -4768,9 +4768,9 @@ type ServerInterface interface { // get changes in commit // (GET /repos/{owner}/{repository}/changes/{commit_id}) GetCommitChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, commitId string, params GetCommitChangesParams) - // get commits in repository + // get commits in ref // (GET /repos/{owner}/{repository}/commits) - GetCommitsInRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetCommitsInRepositoryParams) + GetCommitsInRef(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetCommitsInRefParams) // compare two commit // (GET /repos/{owner}/{repository}/compare/{basehead}) CompareCommit(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, basehead string, params CompareCommitParams) @@ -4905,9 +4905,9 @@ func (_ Unimplemented) GetCommitChanges(ctx context.Context, w *JiaozifsResponse w.WriteHeader(http.StatusNotImplemented) } -// get commits in repository +// get commits in ref // (GET /repos/{owner}/{repository}/commits) -func (_ Unimplemented) GetCommitsInRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetCommitsInRepositoryParams) { +func (_ Unimplemented) GetCommitsInRef(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetCommitsInRefParams) { w.WriteHeader(http.StatusNotImplemented) } @@ -5855,8 +5855,8 @@ func (siw *ServerInterfaceWrapper) GetCommitChanges(w http.ResponseWriter, r *ht handler.ServeHTTP(w, r.WithContext(ctx)) } -// GetCommitsInRepository operation middleware -func (siw *ServerInterfaceWrapper) GetCommitsInRepository(w http.ResponseWriter, r *http.Request) { +// GetCommitsInRef operation middleware +func (siw *ServerInterfaceWrapper) GetCommitsInRef(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -5886,7 +5886,7 @@ func (siw *ServerInterfaceWrapper) GetCommitsInRepository(w http.ResponseWriter, ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context - var params GetCommitsInRepositoryParams + var params GetCommitsInRefParams // ------------- Optional query parameter "after" ------------- @@ -5913,7 +5913,7 @@ func (siw *ServerInterfaceWrapper) GetCommitsInRepository(w http.ResponseWriter, } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetCommitsInRepository(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + siw.Handler.GetCommitsInRef(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -6803,7 +6803,7 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Get(options.BaseURL+"/repos/{owner}/{repository}/changes/{commit_id}", wrapper.GetCommitChanges) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/repos/{owner}/{repository}/commits", wrapper.GetCommitsInRepository) + r.Get(options.BaseURL+"/repos/{owner}/{repository}/commits", wrapper.GetCommitsInRef) }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/repos/{owner}/{repository}/compare/{basehead}", wrapper.CompareCommit) @@ -6857,76 +6857,77 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 86c45c38..2eda877f 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -1101,6 +1101,7 @@ paths: name: path description: path required: false + allowEmptyValue: true schema: type: string responses: @@ -1220,17 +1221,19 @@ paths: name: path description: specific path, if not specific return entries in root required: false + allowEmptyValue: true schema: type: string - in: query name: ref - description: specific branch, default to repostiory default branch(HEAD) + description: specific( ref name, tag name, commit hash), for wip and branchm, branch name default to repository default branch(HEAD), required: false + allowEmptyValue: true schema: type: string - in: query name: type - description: type indicate to retrieve from wip/branch/tag, default branch + description: type indicate to retrieve from wip/branch/tag/commit, default branch required: true schema: $ref: "#/components/schemas/RefType" @@ -1279,6 +1282,7 @@ paths: name: path description: specific path, if not specific return entries in root required: false + allowEmptyValue: true schema: type: string responses: @@ -1324,6 +1328,7 @@ paths: name: path description: specific path, if not specific return entries in root required: false + allowEmptyValue: true schema: type: string responses: @@ -1356,8 +1361,8 @@ paths: get: tags: - repo - operationId: getCommitsInRepository - summary: get commits in repository + operationId: getCommitsInRef + summary: get commits in ref parameters: - $ref: "#/components/parameters/PaginationCommitAfter" - $ref: "#/components/parameters/PaginationAmount" @@ -1365,6 +1370,7 @@ paths: name: refName description: ref(branch/tag) name required: false + allowEmptyValue: true schema: type: string responses: diff --git a/controller/commit_ctl.go b/controller/commit_ctl.go index 3b0179be..e992af5c 100644 --- a/controller/commit_ctl.go +++ b/controller/commit_ctl.go @@ -43,11 +43,6 @@ func (commitCtl CommitController) GetEntriesInRef(ctx context.Context, w *api.Ji return } - refName := repository.HEAD - if params.Ref != nil { - refName = *params.Ref - } - if operator.Name != ownerName { //todo check permission w.Forbidden() return @@ -55,6 +50,11 @@ func (commitCtl CommitController) GetEntriesInRef(ctx context.Context, w *api.Ji treeHash := hash.EmptyHash if params.Type == api.RefTypeWip { + refName := repository.HEAD + if params.Ref != nil { + refName = *params.Ref + } + //todo maybe from tag reference ref, err := commitCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(refName)) if err != nil { @@ -68,6 +68,11 @@ func (commitCtl CommitController) GetEntriesInRef(ctx context.Context, w *api.Ji } treeHash = wip.CurrentTree } else if params.Type == api.RefTypeBranch { + refName := repository.HEAD + if params.Ref != nil { + refName = *params.Ref + } + ref, err := commitCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(refName)) if err != nil { w.Error(err) @@ -81,6 +86,21 @@ func (commitCtl CommitController) GetEntriesInRef(ctx context.Context, w *api.Ji } treeHash = commit.TreeHash } + } else if params.Type == api.RefTypeCommit { + commitHash, err := hash.FromHex(utils.StringValue(params.Ref)) + if err != nil { + w.BadRequest(err.Error()) + return + } + + if !commitHash.IsEmpty() { + commit, err := commitCtl.Repo.CommitRepo(repository.ID).Commit(ctx, commitHash) + if err != nil { + w.Error(err) + return + } + treeHash = commit.TreeHash + } } else { //check in validate middleware, test cant cover here, keep this check w.BadRequest("not support") diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index e0636a4a..90d387ac 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -399,7 +399,7 @@ func (repositoryCtl RepositoryController) UpdateRepository(ctx context.Context, w.OK() } -func (repositoryCtl RepositoryController) GetCommitsInRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.GetCommitsInRepositoryParams) { +func (repositoryCtl RepositoryController) GetCommitsInRef(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.GetCommitsInRefParams) { operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) diff --git a/integrationtest/commit_test.go b/integrationtest/commit_test.go index 116d687c..122b6380 100644 --- a/integrationtest/commit_test.go +++ b/integrationtest/commit_test.go @@ -4,6 +4,8 @@ import ( "context" "net/http" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/jiaozifs/jiaozifs/api" apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" "github.com/jiaozifs/jiaozifs/utils" @@ -227,6 +229,66 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { uploadObject(ctx, c, client, "update f2 to main branch", userName, repoName, "main", "g/m.dat") //modify commitWip(ctx, c, client, "commit branch change", userName, repoName, "main", "test") + c.Convey("get commit entries", func(c convey.C) { + c.Convey("fail to get entries in uncorrected hash", func() { + resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ + Path: utils.String("/"), + Ref: utils.String("123"), + Type: api.RefTypeCommit, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + + c.Convey("fail to get entries in not found", func() { + resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ + Path: utils.String("/"), + Ref: utils.String("46780d412b4b3c71ba6cdfcb52105c7b"), + Type: api.RefTypeCommit, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("success to get entries in empty hash", func() { + resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ + Path: utils.String("/"), + Ref: utils.String(hash.EmptyHash.Hex()), + Type: api.RefTypeCommit, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseGetEntriesInRefResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(*result.JSON200, convey.ShouldHaveLength, 0) + }) + + c.Convey("success to get entries in commit", func() { + getCommitsResp, err := client.GetCommitsInRef(ctx, userName, repoName, &api.GetCommitsInRefParams{ + RefName: utils.String(branchName), + }) + convey.So(err, convey.ShouldBeNil) + getCommitsResult, err := api.ParseGetCommitsInRefResponse(getCommitsResp) + convey.So(err, convey.ShouldBeNil) + + commit := (*getCommitsResult.JSON200)[0] + resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ + Path: utils.String("/"), + Ref: utils.String(commit.Hash), + Type: api.RefTypeCommit, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseGetEntriesInRefResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(*result.JSON200, convey.ShouldHaveLength, 2) + convey.So((*result.JSON200)[0].Name, convey.ShouldEqual, "g") + convey.So((*result.JSON200)[1].Name, convey.ShouldEqual, "m.dat") + }) + }) + c.Convey("compare commit", func(c convey.C) { c.Convey("get base and head", func() { resp, err := client.GetBranch(ctx, userName, repoName, &api.GetBranchParams{RefName: "main"}) @@ -331,10 +393,10 @@ func GetCommitChangesSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("get commit change", func(c convey.C) { c.Convey("list commit history", func() { - resp, err := client.GetCommitsInRepository(ctx, userName, repoName, &api.GetCommitsInRepositoryParams{RefName: utils.String("main")}) + resp, err := client.GetCommitsInRef(ctx, userName, repoName, &api.GetCommitsInRefParams{RefName: utils.String("main")}) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) - result, err := api.ParseGetCommitsInRepositoryResponse(resp) + result, err := api.ParseGetCommitsInRefResponse(resp) convey.So(err, convey.ShouldBeNil) commits = *result.JSON200 diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index 268aa515..2cecc89b 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -365,7 +365,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors client.RequestEditors = nil - resp, err := client.GetCommitsInRepository(ctx, userName, repoName, &api.GetCommitsInRepositoryParams{ + resp, err := client.GetCommitsInRef(ctx, userName, repoName, &api.GetCommitsInRefParams{ RefName: utils.String(controller.DefaultBranchName), }) client.RequestEditors = re @@ -373,7 +373,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("update repository in not exit repo", func() { - resp, err := client.GetCommitsInRepository(ctx, userName, "happyrunfake", &api.GetCommitsInRepositoryParams{ + resp, err := client.GetCommitsInRef(ctx, userName, "happyrunfake", &api.GetCommitsInRefParams{ RefName: utils.String(controller.DefaultBranchName), }) convey.So(err, convey.ShouldBeNil) @@ -381,7 +381,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("update repository in non exit user", func() { - resp, err := client.GetCommitsInRepository(ctx, "telo", repoName, &api.GetCommitsInRepositoryParams{ + resp, err := client.GetCommitsInRef(ctx, "telo", repoName, &api.GetCommitsInRefParams{ RefName: utils.String(controller.DefaultBranchName), }) convey.So(err, convey.ShouldBeNil) @@ -389,7 +389,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("update repository in other's repo", func() { - resp, err := client.GetCommitsInRepository(ctx, "admin", repoName, &api.GetCommitsInRepositoryParams{ + resp, err := client.GetCommitsInRef(ctx, "admin", repoName, &api.GetCommitsInRefParams{ RefName: utils.String(controller.DefaultBranchName), }) convey.So(err, convey.ShouldBeNil) @@ -401,13 +401,13 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { uploadObject(ctx, c, client, "add rand object", userName, repoName, controller.DefaultBranchName, "a.txt") commitWip(ctx, c, client, "commit object", userName, repoName, controller.DefaultBranchName, "first commit") c.Convey("success get commits", func() { - resp, err := client.GetCommitsInRepository(ctx, userName, repoName, &api.GetCommitsInRepositoryParams{ + resp, err := client.GetCommitsInRef(ctx, userName, repoName, &api.GetCommitsInRefParams{ RefName: utils.String(controller.DefaultBranchName), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) - result, err := api.ParseGetCommitsInRepositoryResponse(resp) + result, err := api.ParseGetCommitsInRefResponse(resp) convey.So(err, convey.ShouldBeNil) convey.So(*result.JSON200, convey.ShouldHaveLength, 1) convey.So((*result.JSON200)[0].Message, convey.ShouldEqual, "first commit") @@ -418,18 +418,18 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { uploadObject(ctx, c, client, "add third object", userName, repoName, controller.DefaultBranchName, "c.txt") commitWip(ctx, c, client, "commit third object", userName, repoName, controller.DefaultBranchName, "third commit") c.Convey("success get commits by params", func() { - resp, err := client.GetCommitsInRepository(ctx, userName, repoName, &api.GetCommitsInRepositoryParams{ + resp, err := client.GetCommitsInRef(ctx, userName, repoName, &api.GetCommitsInRefParams{ RefName: utils.String(controller.DefaultBranchName), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) - result, err := api.ParseGetCommitsInRepositoryResponse(resp) + result, err := api.ParseGetCommitsInRefResponse(resp) convey.So(err, convey.ShouldBeNil) convey.So(*result.JSON200, convey.ShouldHaveLength, 3) convey.So((*result.JSON200)[0].Message, convey.ShouldEqual, "third commit") - newResp, err := client.GetCommitsInRepository(ctx, userName, repoName, &api.GetCommitsInRepositoryParams{ + newResp, err := client.GetCommitsInRef(ctx, userName, repoName, &api.GetCommitsInRefParams{ After: utils.String((*result.JSON200)[0].Committer.When.Format(time.RFC3339Nano)), Amount: utils.Int(1), RefName: utils.String(controller.DefaultBranchName), @@ -437,14 +437,14 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) - newResult, err := api.ParseGetCommitsInRepositoryResponse(newResp) + newResult, err := api.ParseGetCommitsInRefResponse(newResp) convey.So(err, convey.ShouldBeNil) convey.So(*newResult.JSON200, convey.ShouldHaveLength, 1) convey.So((*newResult.JSON200)[0].Message, convey.ShouldEqual, "second commit") }) c.Convey("failed get commits by wrong params", func() { - resp, err := client.GetCommitsInRepository(ctx, userName, repoName, &api.GetCommitsInRepositoryParams{ + resp, err := client.GetCommitsInRef(ctx, userName, repoName, &api.GetCommitsInRefParams{ After: utils.String("123"), RefName: utils.String(controller.DefaultBranchName), }) diff --git a/utils/hash/hash.go b/utils/hash/hash.go index 046d949a..c30f72bd 100644 --- a/utils/hash/hash.go +++ b/utils/hash/hash.go @@ -25,7 +25,7 @@ func FromHex(str string) (Hash, error) { func (hash Hash) Hex() string { if hash == nil { - hex.EncodeToString(EmptyHash) + return hex.EncodeToString(EmptyHash) } return hex.EncodeToString(hash) } From a8a2b5201e7dc18eaca550818f2666434e04270e Mon Sep 17 00:00:00 2001 From: taoshengshi Date: Tue, 9 Jan 2024 00:06:18 +0800 Subject: [PATCH 136/210] update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e54919c8..88d3ecee 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# jiaozifs -version control file system. +# JiaoziFS +A version control file system for data centric applications & teams. ## quick start From 0ac7b47bd50a76f1fc8fd717ec20c8c21a5de03a Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Thu, 4 Jan 2024 18:44:28 +0800 Subject: [PATCH 137/210] feat: add models and api stubs --- "\\" | 23 + api/api_impl/impl.go | 1 + api/jiaozifs.gen.go | 3419 ++++++++++++++++++++----------- api/swagger.yml | 222 ++ controller/merge_request_ctl.go | 119 ++ models/merge_request.go | 22 +- models/repo.go | 9 + versionmgr/changes.go | 157 +- versionmgr/conflict.go | 104 + versionmgr/work_repo.go | 175 +- 10 files changed, 2992 insertions(+), 1259 deletions(-) create mode 100644 "\\" create mode 100644 controller/merge_request_ctl.go create mode 100644 versionmgr/conflict.go diff --git "a/\\" "b/\\" new file mode 100644 index 00000000..1a71fdce --- /dev/null +++ "b/\\" @@ -0,0 +1,23 @@ +feat: add models and api stubs + +# Please enter the commit message for your changes. Lines starting +# with '#' will be ignored, and an empty message aborts the commit. +# +# interactive rebase in progress; onto 571ab23 +# Last command done (1 command done): +# pick 541e6f6 feat: add models and api stubs +# Next commands to do (3 remaining commands): +# pick 2d7335b fix +# pick ef83382 feat: impl mergerequest api +# You are currently rebasing branch 'feat/add_mergerequest_api' on '571ab23'. +# +# Changes to be committed: +# modified: api/api_impl/impl.go +# modified: api/jiaozifs.gen.go +# modified: api/swagger.yml +# new file: controller/merge_request_ctl.go +# modified: models/merge_request.go +# modified: versionmgr/changes.go +# new file: versionmgr/conflict.go +# modified: versionmgr/work_repo.go +# diff --git a/api/api_impl/impl.go b/api/api_impl/impl.go index abdadfe2..5b44bab4 100644 --- a/api/api_impl/impl.go +++ b/api/api_impl/impl.go @@ -18,4 +18,5 @@ type APIController struct { controller.CommitController controller.RepositoryController controller.BranchController + controller.MergeRequestController } diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index f2b42b0a..59a16d77 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -114,6 +114,14 @@ type Commit struct { UpdatedAt time.Time `json:"updated_at"` } +// CreateMergeRequest defines model for CreateMergeRequest. +type CreateMergeRequest struct { + Description *string `json:"description,omitempty"` + SourceBranchName string `json:"source_branch_name"` + TargetBranchName string `json:"target_branch_name"` + Title string `json:"title"` +} + // CreateRepository defines model for CreateRepository. type CreateRepository struct { // BlockstoreConfig block storage config url encoded json @@ -162,6 +170,27 @@ type LoginConfig struct { // with an external auth service. type LoginConfigRBAC string +// MergeRequest defines model for MergeRequest. +type MergeRequest struct { + AuthorId openapi_types.UUID `json:"author_id"` + CreatedAt time.Time `json:"created_at"` + Description *string `json:"description,omitempty"` + Id uint64 `json:"id"` + MergeStatus int `json:"merge_status"` + SourceBranch openapi_types.UUID `json:"source_branch"` + SourceRepoId openapi_types.UUID `json:"source_repo_id"` + TargetBranch openapi_types.UUID `json:"target_branch"` + TargetRepoId openapi_types.UUID `json:"target_repo_id"` + Title string `json:"title"` + UpdatedAt time.Time `json:"updated_at"` +} + +// MergeRequestList defines model for MergeRequestList. +type MergeRequestList struct { + Pagination Pagination `json:"pagination"` + Results []MergeRequest `json:"results"` +} + // ObjectStats defines model for ObjectStats. type ObjectStats struct { Checksum string `json:"checksum"` @@ -237,6 +266,12 @@ type Signature struct { When time.Time `json:"when"` } +// UpdateMergeRequest defines model for UpdateMergeRequest. +type UpdateMergeRequest struct { + Description *string `json:"description,omitempty"` + Title *string `json:"title,omitempty"` +} + // UpdateRepository defines model for UpdateRepository. type UpdateRepository struct { Description *string `json:"description,omitempty"` @@ -307,6 +342,18 @@ type LoginJSONBody struct { Password string `json:"password"` } +// ListMergeRequestsParams defines parameters for ListMergeRequests. +type ListMergeRequestsParams struct { + // Prefix return items prefixed with this value + Prefix *PaginationPrefix `form:"prefix,omitempty" json:"prefix,omitempty"` + + // After return items after this value + After *PaginationRepoAfter `form:"after,omitempty" json:"after,omitempty"` + + // Amount how many items to return + Amount *PaginationAmount `form:"amount,omitempty" json:"amount,omitempty"` +} + // DeleteObjectParams defines parameters for DeleteObject. type DeleteObjectParams struct { // RefName branch/tag to the ref @@ -485,6 +532,12 @@ type RevertWipChangesParams struct { // LoginJSONRequestBody defines body for Login for application/json ContentType. type LoginJSONRequestBody LoginJSONBody +// CreateMergeRequestJSONRequestBody defines body for CreateMergeRequest for application/json ContentType. +type CreateMergeRequestJSONRequestBody = CreateMergeRequest + +// UpdateMergeRequestJSONRequestBody defines body for UpdateMergeRequest for application/json ContentType. +type UpdateMergeRequestJSONRequestBody = UpdateMergeRequest + // UploadObjectMultipartRequestBody defines body for UploadObject for multipart/form-data ContentType. type UploadObjectMultipartRequestBody UploadObjectMultipartBody @@ -581,6 +634,25 @@ type ClientInterface interface { // Logout request Logout(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // ListMergeRequests request + ListMergeRequests(ctx context.Context, owner string, repository string, params *ListMergeRequestsParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateMergeRequestWithBody request with any body + CreateMergeRequestWithBody(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateMergeRequest(ctx context.Context, owner string, repository string, body CreateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // DeleteMergeRequest request + DeleteMergeRequest(ctx context.Context, owner string, repository string, mrId uint64, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetMergeRequest request + GetMergeRequest(ctx context.Context, owner string, repository string, mrId uint64, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UpdateMergeRequestWithBody request with any body + UpdateMergeRequestWithBody(ctx context.Context, owner string, repository string, mrId uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + UpdateMergeRequest(ctx context.Context, owner string, repository string, mrId uint64, body UpdateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // DeleteObject request DeleteObject(ctx context.Context, owner string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -710,6 +782,90 @@ func (c *Client) Logout(ctx context.Context, reqEditors ...RequestEditorFn) (*ht return c.Client.Do(req) } +func (c *Client) ListMergeRequests(ctx context.Context, owner string, repository string, params *ListMergeRequestsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListMergeRequestsRequest(c.Server, owner, repository, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateMergeRequestWithBody(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateMergeRequestRequestWithBody(c.Server, owner, repository, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateMergeRequest(ctx context.Context, owner string, repository string, body CreateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateMergeRequestRequest(c.Server, owner, repository, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) DeleteMergeRequest(ctx context.Context, owner string, repository string, mrId uint64, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteMergeRequestRequest(c.Server, owner, repository, mrId) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetMergeRequest(ctx context.Context, owner string, repository string, mrId uint64, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetMergeRequestRequest(c.Server, owner, repository, mrId) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateMergeRequestWithBody(ctx context.Context, owner string, repository string, mrId uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateMergeRequestRequestWithBody(c.Server, owner, repository, mrId, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateMergeRequest(ctx context.Context, owner string, repository string, mrId uint64, body UpdateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateMergeRequestRequest(c.Server, owner, repository, mrId, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) DeleteObject(ctx context.Context, owner string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewDeleteObjectRequest(c.Server, owner, repository, params) if err != nil { @@ -1161,8 +1317,8 @@ func NewLogoutRequest(server string) (*http.Request, error) { return req, nil } -// NewDeleteObjectRequest generates requests for DeleteObject -func NewDeleteObjectRequest(server string, owner string, repository string, params *DeleteObjectParams) (*http.Request, error) { +// NewListMergeRequestsRequest generates requests for ListMergeRequests +func NewListMergeRequestsRequest(server string, owner string, repository string, params *ListMergeRequestsParams) (*http.Request, error) { var err error var pathParam0 string @@ -1184,7 +1340,7 @@ func NewDeleteObjectRequest(server string, owner string, repository string, para return nil, err } - operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/mergequest/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1197,34 +1353,58 @@ func NewDeleteObjectRequest(server string, owner string, repository string, para if params != nil { queryValues := queryURL.Query() - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if params.Prefix != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "prefix", runtime.ParamLocationQuery, *params.Prefix); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } + } - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if params.After != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "after", runtime.ParamLocationQuery, *params.After); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Amount != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "amount", runtime.ParamLocationQuery, *params.Amount); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } + } queryURL.RawQuery = queryValues.Encode() } - req, err := http.NewRequest("DELETE", queryURL.String(), nil) + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } @@ -1232,8 +1412,19 @@ func NewDeleteObjectRequest(server string, owner string, repository string, para return req, nil } -// NewGetObjectRequest generates requests for GetObject -func NewGetObjectRequest(server string, owner string, repository string, params *GetObjectParams) (*http.Request, error) { +// NewCreateMergeRequestRequest calls the generic CreateMergeRequest builder with application/json body +func NewCreateMergeRequestRequest(server string, owner string, repository string, body CreateMergeRequestJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateMergeRequestRequestWithBody(server, owner, repository, "application/json", bodyReader) +} + +// NewCreateMergeRequestRequestWithBody generates requests for CreateMergeRequest with any type of body +func NewCreateMergeRequestRequestWithBody(server string, owner string, repository string, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -1255,7 +1446,7 @@ func NewGetObjectRequest(server string, owner string, repository string, params return nil, err } - operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/mergequest/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1265,73 +1456,18 @@ func NewGetObjectRequest(server string, owner string, repository string, params return nil, err } - if params != nil { - queryValues := queryURL.Query() - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "type", runtime.ParamLocationQuery, params.Type); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - queryURL.RawQuery = queryValues.Encode() - } - - req, err := http.NewRequest("GET", queryURL.String(), nil) + req, err := http.NewRequest("POST", queryURL.String(), body) if err != nil { return nil, err } - if params != nil { - - if params.Range != nil { - var headerParam0 string - - headerParam0, err = runtime.StyleParamWithLocation("simple", false, "Range", runtime.ParamLocationHeader, *params.Range) - if err != nil { - return nil, err - } - - req.Header.Set("Range", headerParam0) - } - - } + req.Header.Add("Content-Type", contentType) return req, nil } -// NewHeadObjectRequest generates requests for HeadObject -func NewHeadObjectRequest(server string, owner string, repository string, params *HeadObjectParams) (*http.Request, error) { +// NewDeleteMergeRequestRequest generates requests for DeleteMergeRequest +func NewDeleteMergeRequestRequest(server string, owner string, repository string, mrId uint64) (*http.Request, error) { var err error var pathParam0 string @@ -1348,12 +1484,19 @@ func NewHeadObjectRequest(server string, owner string, repository string, params return nil, err } + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "mrId", runtime.ParamLocationPath, mrId) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/mergequest/%s/%s/mr/%s", pathParam0, pathParam1, pathParam2) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1363,146 +1506,16 @@ func NewHeadObjectRequest(server string, owner string, repository string, params return nil, err } - if params != nil { - queryValues := queryURL.Query() - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "type", runtime.ParamLocationQuery, params.Type); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - queryURL.RawQuery = queryValues.Encode() - } - - req, err := http.NewRequest("HEAD", queryURL.String(), nil) - if err != nil { - return nil, err - } - - if params != nil { - - if params.Range != nil { - var headerParam0 string - - headerParam0, err = runtime.StyleParamWithLocation("simple", false, "Range", runtime.ParamLocationHeader, *params.Range) - if err != nil { - return nil, err - } - - req.Header.Set("Range", headerParam0) - } - - } - - return req, nil -} - -// NewUploadObjectRequestWithBody generates requests for UploadObject with any type of body -func NewUploadObjectRequestWithBody(server string, owner string, repository string, params *UploadObjectParams, contentType string, body io.Reader) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) - if err != nil { - return nil, err - } - - var pathParam1 string - - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - if params != nil { - queryValues := queryURL.Query() - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - queryURL.RawQuery = queryValues.Encode() - } - - req, err := http.NewRequest("POST", queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } return req, nil } -// NewDeleteRepositoryRequest generates requests for DeleteRepository -func NewDeleteRepositoryRequest(server string, owner string, repository string) (*http.Request, error) { +// NewGetMergeRequestRequest generates requests for GetMergeRequest +func NewGetMergeRequestRequest(server string, owner string, repository string, mrId uint64) (*http.Request, error) { var err error var pathParam0 string @@ -1519,43 +1532,9 @@ func NewDeleteRepositoryRequest(server string, owner string, repository string) return nil, err } - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/repos/%s/%s", pathParam0, pathParam1) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("DELETE", queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewGetRepositoryRequest generates requests for GetRepository -func NewGetRepositoryRequest(server string, owner string, repository string) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) - if err != nil { - return nil, err - } - - var pathParam1 string + var pathParam2 string - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "mrId", runtime.ParamLocationPath, mrId) if err != nil { return nil, err } @@ -1565,7 +1544,7 @@ func NewGetRepositoryRequest(server string, owner string, repository string) (*h return nil, err } - operationPath := fmt.Sprintf("/repos/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/mergequest/%s/%s/mr/%s", pathParam0, pathParam1, pathParam2) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1583,19 +1562,19 @@ func NewGetRepositoryRequest(server string, owner string, repository string) (*h return req, nil } -// NewUpdateRepositoryRequest calls the generic UpdateRepository builder with application/json body -func NewUpdateRepositoryRequest(server string, owner string, repository string, body UpdateRepositoryJSONRequestBody) (*http.Request, error) { +// NewUpdateMergeRequestRequest calls the generic UpdateMergeRequest builder with application/json body +func NewUpdateMergeRequestRequest(server string, owner string, repository string, mrId uint64, body UpdateMergeRequestJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewUpdateRepositoryRequestWithBody(server, owner, repository, "application/json", bodyReader) + return NewUpdateMergeRequestRequestWithBody(server, owner, repository, mrId, "application/json", bodyReader) } -// NewUpdateRepositoryRequestWithBody generates requests for UpdateRepository with any type of body -func NewUpdateRepositoryRequestWithBody(server string, owner string, repository string, contentType string, body io.Reader) (*http.Request, error) { +// NewUpdateMergeRequestRequestWithBody generates requests for UpdateMergeRequest with any type of body +func NewUpdateMergeRequestRequestWithBody(server string, owner string, repository string, mrId uint64, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -1612,12 +1591,19 @@ func NewUpdateRepositoryRequestWithBody(server string, owner string, repository return nil, err } + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "mrId", runtime.ParamLocationPath, mrId) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/repos/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/mergequest/%s/%s/mr/%s", pathParam0, pathParam1, pathParam2) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1637,8 +1623,8 @@ func NewUpdateRepositoryRequestWithBody(server string, owner string, repository return req, nil } -// NewDeleteBranchRequest generates requests for DeleteBranch -func NewDeleteBranchRequest(server string, owner string, repository string, params *DeleteBranchParams) (*http.Request, error) { +// NewDeleteObjectRequest generates requests for DeleteObject +func NewDeleteObjectRequest(server string, owner string, repository string, params *DeleteObjectParams) (*http.Request, error) { var err error var pathParam0 string @@ -1660,7 +1646,7 @@ func NewDeleteBranchRequest(server string, owner string, repository string, para return nil, err } - operationPath := fmt.Sprintf("/repos/%s/%s/branch", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1685,6 +1671,18 @@ func NewDeleteBranchRequest(server string, owner string, repository string, para } } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + queryURL.RawQuery = queryValues.Encode() } @@ -1696,8 +1694,8 @@ func NewDeleteBranchRequest(server string, owner string, repository string, para return req, nil } -// NewGetBranchRequest generates requests for GetBranch -func NewGetBranchRequest(server string, owner string, repository string, params *GetBranchParams) (*http.Request, error) { +// NewGetObjectRequest generates requests for GetObject +func NewGetObjectRequest(server string, owner string, repository string, params *GetObjectParams) (*http.Request, error) { var err error var pathParam0 string @@ -1719,7 +1717,7 @@ func NewGetBranchRequest(server string, owner string, repository string, params return nil, err } - operationPath := fmt.Sprintf("/repos/%s/%s/branch", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1732,6 +1730,18 @@ func NewGetBranchRequest(server string, owner string, repository string, params if params != nil { queryValues := queryURL.Query() + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "type", runtime.ParamLocationQuery, params.Type); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { @@ -1744,6 +1754,18 @@ func NewGetBranchRequest(server string, owner string, repository string, params } } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + queryURL.RawQuery = queryValues.Encode() } @@ -1752,22 +1774,26 @@ func NewGetBranchRequest(server string, owner string, repository string, params return nil, err } - return req, nil -} + if params != nil { + + if params.Range != nil { + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithLocation("simple", false, "Range", runtime.ParamLocationHeader, *params.Range) + if err != nil { + return nil, err + } + + req.Header.Set("Range", headerParam0) + } -// NewCreateBranchRequest calls the generic CreateBranch builder with application/json body -func NewCreateBranchRequest(server string, owner string, repository string, body CreateBranchJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err } - bodyReader = bytes.NewReader(buf) - return NewCreateBranchRequestWithBody(server, owner, repository, "application/json", bodyReader) + + return req, nil } -// NewCreateBranchRequestWithBody generates requests for CreateBranch with any type of body -func NewCreateBranchRequestWithBody(server string, owner string, repository string, contentType string, body io.Reader) (*http.Request, error) { +// NewHeadObjectRequest generates requests for HeadObject +func NewHeadObjectRequest(server string, owner string, repository string, params *HeadObjectParams) (*http.Request, error) { var err error var pathParam0 string @@ -1789,7 +1815,7 @@ func NewCreateBranchRequestWithBody(server string, owner string, repository stri return nil, err } - operationPath := fmt.Sprintf("/repos/%s/%s/branch", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1799,18 +1825,73 @@ func NewCreateBranchRequestWithBody(server string, owner string, repository stri return nil, err } - req, err := http.NewRequest("POST", queryURL.String(), body) - if err != nil { + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "type", runtime.ParamLocationQuery, params.Type); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("HEAD", queryURL.String(), nil) + if err != nil { return nil, err } - req.Header.Add("Content-Type", contentType) + if params != nil { + + if params.Range != nil { + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithLocation("simple", false, "Range", runtime.ParamLocationHeader, *params.Range) + if err != nil { + return nil, err + } + + req.Header.Set("Range", headerParam0) + } + + } return req, nil } -// NewListBranchesRequest generates requests for ListBranches -func NewListBranchesRequest(server string, owner string, repository string, params *ListBranchesParams) (*http.Request, error) { +// NewUploadObjectRequestWithBody generates requests for UploadObject with any type of body +func NewUploadObjectRequestWithBody(server string, owner string, repository string, params *UploadObjectParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -1832,7 +1913,7 @@ func NewListBranchesRequest(server string, owner string, repository string, para return nil, err } - operationPath := fmt.Sprintf("/repos/%s/%s/branches", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1845,67 +1926,45 @@ func NewListBranchesRequest(server string, owner string, repository string, para if params != nil { queryValues := queryURL.Query() - if params.Prefix != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "prefix", runtime.ParamLocationQuery, *params.Prefix); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - if params.After != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "after", runtime.ParamLocationQuery, *params.After); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) } } - } - if params.Amount != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "amount", runtime.ParamLocationQuery, *params.Amount); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) } } - } queryURL.RawQuery = queryValues.Encode() } - req, err := http.NewRequest("GET", queryURL.String(), nil) + req, err := http.NewRequest("POST", queryURL.String(), body) if err != nil { return nil, err } + req.Header.Add("Content-Type", contentType) + return req, nil } -// NewGetCommitChangesRequest generates requests for GetCommitChanges -func NewGetCommitChangesRequest(server string, owner string, repository string, commitId string, params *GetCommitChangesParams) (*http.Request, error) { +// NewDeleteRepositoryRequest generates requests for DeleteRepository +func NewDeleteRepositoryRequest(server string, owner string, repository string) (*http.Request, error) { var err error var pathParam0 string @@ -1922,19 +1981,12 @@ func NewGetCommitChangesRequest(server string, owner string, repository string, return nil, err } - var pathParam2 string - - pathParam2, err = runtime.StyleParamWithLocation("simple", false, "commit_id", runtime.ParamLocationPath, commitId) - if err != nil { - return nil, err - } - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/repos/%s/%s/changes/%s", pathParam0, pathParam1, pathParam2) + operationPath := fmt.Sprintf("/repos/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1944,29 +1996,7 @@ func NewGetCommitChangesRequest(server string, owner string, repository string, return nil, err } - if params != nil { - queryValues := queryURL.Query() - - if params.Path != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - queryURL.RawQuery = queryValues.Encode() - } - - req, err := http.NewRequest("GET", queryURL.String(), nil) + req, err := http.NewRequest("DELETE", queryURL.String(), nil) if err != nil { return nil, err } @@ -1974,8 +2004,8 @@ func NewGetCommitChangesRequest(server string, owner string, repository string, return req, nil } -// NewGetCommitsInRefRequest generates requests for GetCommitsInRef -func NewGetCommitsInRefRequest(server string, owner string, repository string, params *GetCommitsInRefParams) (*http.Request, error) { +// NewGetRepositoryRequest generates requests for GetRepository +func NewGetRepositoryRequest(server string, owner string, repository string) (*http.Request, error) { var err error var pathParam0 string @@ -1997,7 +2027,7 @@ func NewGetCommitsInRefRequest(server string, owner string, repository string, p return nil, err } - operationPath := fmt.Sprintf("/repos/%s/%s/commits", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/repos/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2007,60 +2037,6 @@ func NewGetCommitsInRefRequest(server string, owner string, repository string, p return nil, err } - if params != nil { - queryValues := queryURL.Query() - - if params.After != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "after", runtime.ParamLocationQuery, *params.After); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - if params.Amount != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "amount", runtime.ParamLocationQuery, *params.Amount); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - if params.RefName != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, *params.RefName); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - queryURL.RawQuery = queryValues.Encode() - } - req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err @@ -2069,8 +2045,19 @@ func NewGetCommitsInRefRequest(server string, owner string, repository string, p return req, nil } -// NewCompareCommitRequest generates requests for CompareCommit -func NewCompareCommitRequest(server string, owner string, repository string, basehead string, params *CompareCommitParams) (*http.Request, error) { +// NewUpdateRepositoryRequest calls the generic UpdateRepository builder with application/json body +func NewUpdateRepositoryRequest(server string, owner string, repository string, body UpdateRepositoryJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewUpdateRepositoryRequestWithBody(server, owner, repository, "application/json", bodyReader) +} + +// NewUpdateRepositoryRequestWithBody generates requests for UpdateRepository with any type of body +func NewUpdateRepositoryRequestWithBody(server string, owner string, repository string, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -2087,19 +2074,12 @@ func NewCompareCommitRequest(server string, owner string, repository string, bas return nil, err } - var pathParam2 string - - pathParam2, err = runtime.StyleParamWithLocation("simple", false, "basehead", runtime.ParamLocationPath, basehead) - if err != nil { - return nil, err - } - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/repos/%s/%s/compare/%s", pathParam0, pathParam1, pathParam2) + operationPath := fmt.Sprintf("/repos/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2109,38 +2089,18 @@ func NewCompareCommitRequest(server string, owner string, repository string, bas return nil, err } - if params != nil { - queryValues := queryURL.Query() + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } - if params.Path != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - queryURL.RawQuery = queryValues.Encode() - } - - req, err := http.NewRequest("GET", queryURL.String(), nil) - if err != nil { - return nil, err - } + req.Header.Add("Content-Type", contentType) return req, nil } -// NewGetEntriesInRefRequest generates requests for GetEntriesInRef -func NewGetEntriesInRefRequest(server string, owner string, repository string, params *GetEntriesInRefParams) (*http.Request, error) { +// NewDeleteBranchRequest generates requests for DeleteBranch +func NewDeleteBranchRequest(server string, owner string, repository string, params *DeleteBranchParams) (*http.Request, error) { var err error var pathParam0 string @@ -2162,7 +2122,7 @@ func NewGetEntriesInRefRequest(server string, owner string, repository string, p return nil, err } - operationPath := fmt.Sprintf("/repos/%s/%s/contents", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/repos/%s/%s/branch", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2175,39 +2135,7 @@ func NewGetEntriesInRefRequest(server string, owner string, repository string, p if params != nil { queryValues := queryURL.Query() - if params.Path != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - if params.Ref != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "ref", runtime.ParamLocationQuery, *params.Ref); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "type", runtime.ParamLocationQuery, params.Type); err != nil { + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -2222,7 +2150,7 @@ func NewGetEntriesInRefRequest(server string, owner string, repository string, p queryURL.RawQuery = queryValues.Encode() } - req, err := http.NewRequest("GET", queryURL.String(), nil) + req, err := http.NewRequest("DELETE", queryURL.String(), nil) if err != nil { return nil, err } @@ -2230,16 +2158,30 @@ func NewGetEntriesInRefRequest(server string, owner string, repository string, p return req, nil } -// NewGetSetupStateRequest generates requests for GetSetupState -func NewGetSetupStateRequest(server string) (*http.Request, error) { +// NewGetBranchRequest generates requests for GetBranch +func NewGetBranchRequest(server string, owner string, repository string, params *GetBranchParams) (*http.Request, error) { var err error + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/setup") + operationPath := fmt.Sprintf("/repos/%s/%s/branch", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2249,6 +2191,24 @@ func NewGetSetupStateRequest(server string) (*http.Request, error) { return nil, err } + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err @@ -2257,27 +2217,41 @@ func NewGetSetupStateRequest(server string) (*http.Request, error) { return req, nil } -// NewRegisterRequest calls the generic Register builder with application/json body -func NewRegisterRequest(server string, body RegisterJSONRequestBody) (*http.Request, error) { +// NewCreateBranchRequest calls the generic CreateBranch builder with application/json body +func NewCreateBranchRequest(server string, owner string, repository string, body CreateBranchJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewRegisterRequestWithBody(server, "application/json", bodyReader) + return NewCreateBranchRequestWithBody(server, owner, repository, "application/json", bodyReader) } -// NewRegisterRequestWithBody generates requests for Register with any type of body -func NewRegisterRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { +// NewCreateBranchRequestWithBody generates requests for CreateBranch with any type of body +func NewCreateBranchRequestWithBody(server string, owner string, repository string, contentType string, body io.Reader) (*http.Request, error) { var err error + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/users/register") + operationPath := fmt.Sprintf("/repos/%s/%s/branch", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2297,16 +2271,30 @@ func NewRegisterRequestWithBody(server string, contentType string, body io.Reade return req, nil } -// NewListRepositoryOfAuthenticatedUserRequest generates requests for ListRepositoryOfAuthenticatedUser -func NewListRepositoryOfAuthenticatedUserRequest(server string, params *ListRepositoryOfAuthenticatedUserParams) (*http.Request, error) { +// NewListBranchesRequest generates requests for ListBranches +func NewListBranchesRequest(server string, owner string, repository string, params *ListBranchesParams) (*http.Request, error) { var err error + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/users/repos") + operationPath := fmt.Sprintf("/repos/%s/%s/branches", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2378,56 +2366,37 @@ func NewListRepositoryOfAuthenticatedUserRequest(server string, params *ListRepo return req, nil } -// NewCreateRepositoryRequest calls the generic CreateRepository builder with application/json body -func NewCreateRepositoryRequest(server string, body CreateRepositoryJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewCreateRepositoryRequestWithBody(server, "application/json", bodyReader) -} - -// NewCreateRepositoryRequestWithBody generates requests for CreateRepository with any type of body -func NewCreateRepositoryRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { +// NewGetCommitChangesRequest generates requests for GetCommitChanges +func NewGetCommitChangesRequest(server string, owner string, repository string, commitId string, params *GetCommitChangesParams) (*http.Request, error) { var err error - serverURL, err := url.Parse(server) + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/users/repos") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } + var pathParam1 string - queryURL, err := serverURL.Parse(operationPath) + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) if err != nil { return nil, err } - req, err := http.NewRequest("POST", queryURL.String(), body) + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "commit_id", runtime.ParamLocationPath, commitId) if err != nil { return nil, err } - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewGetUserInfoRequest generates requests for GetUserInfo -func NewGetUserInfoRequest(server string) (*http.Request, error) { - var err error - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/users/user") + operationPath := fmt.Sprintf("/repos/%s/%s/changes/%s", pathParam0, pathParam1, pathParam2) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2437,6 +2406,28 @@ func NewGetUserInfoRequest(server string) (*http.Request, error) { return nil, err } + if params != nil { + queryValues := queryURL.Query() + + if params.Path != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err @@ -2445,8 +2436,8 @@ func NewGetUserInfoRequest(server string) (*http.Request, error) { return req, nil } -// NewListRepositoryRequest generates requests for ListRepository -func NewListRepositoryRequest(server string, owner string, params *ListRepositoryParams) (*http.Request, error) { +// NewGetCommitsInRefRequest generates requests for GetCommitsInRef +func NewGetCommitsInRefRequest(server string, owner string, repository string, params *GetCommitsInRefParams) (*http.Request, error) { var err error var pathParam0 string @@ -2456,12 +2447,19 @@ func NewListRepositoryRequest(server string, owner string, params *ListRepositor return nil, err } + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/users/%s/repos", pathParam0) + operationPath := fmt.Sprintf("/repos/%s/%s/commits", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2474,9 +2472,9 @@ func NewListRepositoryRequest(server string, owner string, params *ListRepositor if params != nil { queryValues := queryURL.Query() - if params.Prefix != nil { + if params.After != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "prefix", runtime.ParamLocationQuery, *params.Prefix); err != nil { + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "after", runtime.ParamLocationQuery, *params.After); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -2490,9 +2488,9 @@ func NewListRepositoryRequest(server string, owner string, params *ListRepositor } - if params.After != nil { + if params.Amount != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "after", runtime.ParamLocationQuery, *params.After); err != nil { + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "amount", runtime.ParamLocationQuery, *params.Amount); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -2506,9 +2504,9 @@ func NewListRepositoryRequest(server string, owner string, params *ListRepositor } - if params.Amount != nil { + if params.RefName != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "amount", runtime.ParamLocationQuery, *params.Amount); err != nil { + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, *params.RefName); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -2533,57 +2531,37 @@ func NewListRepositoryRequest(server string, owner string, params *ListRepositor return req, nil } -// NewGetVersionRequest generates requests for GetVersion -func NewGetVersionRequest(server string) (*http.Request, error) { +// NewCompareCommitRequest generates requests for CompareCommit +func NewCompareCommitRequest(server string, owner string, repository string, basehead string, params *CompareCommitParams) (*http.Request, error) { var err error - serverURL, err := url.Parse(server) + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/version") - if operationPath[0] == '/' { - operationPath = "." + operationPath + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err } - queryURL, err := serverURL.Parse(operationPath) + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "basehead", runtime.ParamLocationPath, basehead) if err != nil { return nil, err } - req, err := http.NewRequest("GET", queryURL.String(), nil) + serverURL, err := url.Parse(server) if err != nil { return nil, err } - return req, nil -} - -// NewDeleteWipRequest generates requests for DeleteWip -func NewDeleteWipRequest(server string, owner string, repository string, params *DeleteWipParams) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) - if err != nil { - return nil, err - } - - var pathParam1 string - - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/wip/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/repos/%s/%s/compare/%s", pathParam0, pathParam1, pathParam2) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2596,22 +2574,26 @@ func NewDeleteWipRequest(server string, owner string, repository string, params if params != nil { queryValues := queryURL.Query() - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if params.Path != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } + } queryURL.RawQuery = queryValues.Encode() } - req, err := http.NewRequest("DELETE", queryURL.String(), nil) + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } @@ -2619,8 +2601,8 @@ func NewDeleteWipRequest(server string, owner string, repository string, params return req, nil } -// NewGetWipRequest generates requests for GetWip -func NewGetWipRequest(server string, owner string, repository string, params *GetWipParams) (*http.Request, error) { +// NewGetEntriesInRefRequest generates requests for GetEntriesInRef +func NewGetEntriesInRefRequest(server string, owner string, repository string, params *GetEntriesInRefParams) (*http.Request, error) { var err error var pathParam0 string @@ -2642,7 +2624,7 @@ func NewGetWipRequest(server string, owner string, repository string, params *Ge return nil, err } - operationPath := fmt.Sprintf("/wip/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/repos/%s/%s/contents", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2655,7 +2637,39 @@ func NewGetWipRequest(server string, owner string, repository string, params *Ge if params != nil { queryValues := queryURL.Query() - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + if params.Path != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Ref != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "ref", runtime.ParamLocationQuery, *params.Ref); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "type", runtime.ParamLocationQuery, params.Type); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -2678,30 +2692,83 @@ func NewGetWipRequest(server string, owner string, repository string, params *Ge return req, nil } -// NewGetWipChangesRequest generates requests for GetWipChanges -func NewGetWipChangesRequest(server string, owner string, repository string, params *GetWipChangesParams) (*http.Request, error) { +// NewGetSetupStateRequest generates requests for GetSetupState +func NewGetSetupStateRequest(server string) (*http.Request, error) { var err error - var pathParam0 string + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + operationPath := fmt.Sprintf("/setup") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) if err != nil { return nil, err } - var pathParam1 string + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + return req, nil +} + +// NewRegisterRequest calls the generic Register builder with application/json body +func NewRegisterRequest(server string, body RegisterJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) if err != nil { return nil, err } + bodyReader = bytes.NewReader(buf) + return NewRegisterRequestWithBody(server, "application/json", bodyReader) +} + +// NewRegisterRequestWithBody generates requests for Register with any type of body +func NewRegisterRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/wip/%s/%s/changes", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/users/register") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewListRepositoryOfAuthenticatedUserRequest generates requests for ListRepositoryOfAuthenticatedUser +func NewListRepositoryOfAuthenticatedUserRequest(server string, params *ListRepositoryOfAuthenticatedUserParams) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/users/repos") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2714,21 +2781,41 @@ func NewGetWipChangesRequest(server string, owner string, repository string, par if params != nil { queryValues := queryURL.Query() - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if params.Prefix != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "prefix", runtime.ParamLocationQuery, *params.Prefix); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } + } - if params.Path != nil { + if params.After != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "after", runtime.ParamLocationQuery, *params.After); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Amount != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "amount", runtime.ParamLocationQuery, *params.Amount); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -2753,30 +2840,27 @@ func NewGetWipChangesRequest(server string, owner string, repository string, par return req, nil } -// NewCommitWipRequest generates requests for CommitWip -func NewCommitWipRequest(server string, owner string, repository string, params *CommitWipParams) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) +// NewCreateRepositoryRequest calls the generic CreateRepository builder with application/json body +func NewCreateRepositoryRequest(server string, body CreateRepositoryJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) if err != nil { return nil, err } + bodyReader = bytes.NewReader(buf) + return NewCreateRepositoryRequestWithBody(server, "application/json", bodyReader) +} - var pathParam1 string - - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) - if err != nil { - return nil, err - } +// NewCreateRepositoryRequestWithBody generates requests for CreateRepository with any type of body +func NewCreateRepositoryRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/wip/%s/%s/commit", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/users/repos") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2786,46 +2870,45 @@ func NewCommitWipRequest(server string, owner string, repository string, params return nil, err } - if params != nil { - queryValues := queryURL.Query() - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "msg", runtime.ParamLocationQuery, params.Msg); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - queryURL.RawQuery = queryValues.Encode() - } - - req, err := http.NewRequest("POST", queryURL.String(), nil) + req, err := http.NewRequest("POST", queryURL.String(), body) if err != nil { return nil, err } + req.Header.Add("Content-Type", contentType) + return req, nil } -// NewListWipRequest generates requests for ListWip -func NewListWipRequest(server string, owner string, repository string) (*http.Request, error) { +// NewGetUserInfoRequest generates requests for GetUserInfo +func NewGetUserInfoRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/users/user") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewListRepositoryRequest generates requests for ListRepository +func NewListRepositoryRequest(server string, owner string, params *ListRepositoryParams) (*http.Request, error) { var err error var pathParam0 string @@ -2835,19 +2918,93 @@ func NewListWipRequest(server string, owner string, repository string) (*http.Re return nil, err } - var pathParam1 string + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + operationPath := fmt.Sprintf("/users/%s/repos", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Prefix != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "prefix", runtime.ParamLocationQuery, *params.Prefix); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.After != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "after", runtime.ParamLocationQuery, *params.After); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Amount != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "amount", runtime.ParamLocationQuery, *params.Amount); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } + return req, nil +} + +// NewGetVersionRequest generates requests for GetVersion +func NewGetVersionRequest(server string) (*http.Request, error) { + var err error + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/wip/%s/%s/list", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/version") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2865,8 +3022,8 @@ func NewListWipRequest(server string, owner string, repository string) (*http.Re return req, nil } -// NewRevertWipChangesRequest generates requests for RevertWipChanges -func NewRevertWipChangesRequest(server string, owner string, repository string, params *RevertWipChangesParams) (*http.Request, error) { +// NewDeleteWipRequest generates requests for DeleteWip +func NewDeleteWipRequest(server string, owner string, repository string, params *DeleteWipParams) (*http.Request, error) { var err error var pathParam0 string @@ -2888,7 +3045,7 @@ func NewRevertWipChangesRequest(server string, owner string, repository string, return nil, err } - operationPath := fmt.Sprintf("/wip/%s/%s/revert", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/wip/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2913,26 +3070,10 @@ func NewRevertWipChangesRequest(server string, owner string, repository string, } } - if params.PathPrefix != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "pathPrefix", runtime.ParamLocationQuery, *params.PathPrefix); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - queryURL.RawQuery = queryValues.Encode() } - req, err := http.NewRequest("POST", queryURL.String(), nil) + req, err := http.NewRequest("DELETE", queryURL.String(), nil) if err != nil { return nil, err } @@ -2940,285 +3081,498 @@ func NewRevertWipChangesRequest(server string, owner string, repository string, return req, nil } -func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { - for _, r := range c.RequestEditors { - if err := r(ctx, req); err != nil { - return err - } - } - for _, r := range additionalEditors { - if err := r(ctx, req); err != nil { - return err - } - } - return nil -} +// NewGetWipRequest generates requests for GetWip +func NewGetWipRequest(server string, owner string, repository string, params *GetWipParams) (*http.Request, error) { + var err error -// ClientWithResponses builds on ClientInterface to offer response payloads -type ClientWithResponses struct { - ClientInterface -} + var pathParam0 string -// NewClientWithResponses creates a new ClientWithResponses, which wraps -// Client with return type handling -func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { - client, err := NewClient(server, opts...) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } - return &ClientWithResponses{client}, nil -} -// WithBaseURL overrides the baseURL. -func WithBaseURL(baseURL string) ClientOption { - return func(c *Client) error { - newBaseURL, err := url.Parse(baseURL) - if err != nil { - return err - } - c.Server = newBaseURL.String() - return nil + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err } -} -// ClientWithResponsesInterface is the interface specification for the client with responses above. -type ClientWithResponsesInterface interface { - // LoginWithBodyWithResponse request with any body - LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } - LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) + operationPath := fmt.Sprintf("/wip/%s/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } - // LogoutWithResponse request - LogoutWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*LogoutResponse, error) + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } - // DeleteObjectWithResponse request - DeleteObjectWithResponse(ctx context.Context, owner string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) + if params != nil { + queryValues := queryURL.Query() - // GetObjectWithResponse request - GetObjectWithResponse(ctx context.Context, owner string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*GetObjectResponse, error) + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } - // HeadObjectWithResponse request - HeadObjectWithResponse(ctx context.Context, owner string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*HeadObjectResponse, error) + queryURL.RawQuery = queryValues.Encode() + } - // UploadObjectWithBodyWithResponse request with any body - UploadObjectWithBodyWithResponse(ctx context.Context, owner string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } - // DeleteRepositoryWithResponse request - DeleteRepositoryWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*DeleteRepositoryResponse, error) + return req, nil +} - // GetRepositoryWithResponse request - GetRepositoryWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*GetRepositoryResponse, error) +// NewGetWipChangesRequest generates requests for GetWipChanges +func NewGetWipChangesRequest(server string, owner string, repository string, params *GetWipChangesParams) (*http.Request, error) { + var err error - // UpdateRepositoryWithBodyWithResponse request with any body - UpdateRepositoryWithBodyWithResponse(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) + var pathParam0 string - UpdateRepositoryWithResponse(ctx context.Context, owner string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err + } - // DeleteBranchWithResponse request - DeleteBranchWithResponse(ctx context.Context, owner string, repository string, params *DeleteBranchParams, reqEditors ...RequestEditorFn) (*DeleteBranchResponse, error) + var pathParam1 string - // GetBranchWithResponse request - GetBranchWithResponse(ctx context.Context, owner string, repository string, params *GetBranchParams, reqEditors ...RequestEditorFn) (*GetBranchResponse, error) + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } - // CreateBranchWithBodyWithResponse request with any body - CreateBranchWithBodyWithResponse(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } - CreateBranchWithResponse(ctx context.Context, owner string, repository string, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) - - // ListBranchesWithResponse request - ListBranchesWithResponse(ctx context.Context, owner string, repository string, params *ListBranchesParams, reqEditors ...RequestEditorFn) (*ListBranchesResponse, error) + operationPath := fmt.Sprintf("/wip/%s/%s/changes", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } - // GetCommitChangesWithResponse request - GetCommitChangesWithResponse(ctx context.Context, owner string, repository string, commitId string, params *GetCommitChangesParams, reqEditors ...RequestEditorFn) (*GetCommitChangesResponse, error) + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } - // GetCommitsInRefWithResponse request - GetCommitsInRefWithResponse(ctx context.Context, owner string, repository string, params *GetCommitsInRefParams, reqEditors ...RequestEditorFn) (*GetCommitsInRefResponse, error) + if params != nil { + queryValues := queryURL.Query() - // CompareCommitWithResponse request - CompareCommitWithResponse(ctx context.Context, owner string, repository string, basehead string, params *CompareCommitParams, reqEditors ...RequestEditorFn) (*CompareCommitResponse, error) + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } - // GetEntriesInRefWithResponse request - GetEntriesInRefWithResponse(ctx context.Context, owner string, repository string, params *GetEntriesInRefParams, reqEditors ...RequestEditorFn) (*GetEntriesInRefResponse, error) + if params.Path != nil { - // GetSetupStateWithResponse request - GetSetupStateWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetSetupStateResponse, error) + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } - // RegisterWithBodyWithResponse request with any body - RegisterWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterResponse, error) + } - RegisterWithResponse(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterResponse, error) + queryURL.RawQuery = queryValues.Encode() + } - // ListRepositoryOfAuthenticatedUserWithResponse request - ListRepositoryOfAuthenticatedUserWithResponse(ctx context.Context, params *ListRepositoryOfAuthenticatedUserParams, reqEditors ...RequestEditorFn) (*ListRepositoryOfAuthenticatedUserResponse, error) + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } - // CreateRepositoryWithBodyWithResponse request with any body - CreateRepositoryWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateRepositoryResponse, error) + return req, nil +} - CreateRepositoryWithResponse(ctx context.Context, body CreateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateRepositoryResponse, error) +// NewCommitWipRequest generates requests for CommitWip +func NewCommitWipRequest(server string, owner string, repository string, params *CommitWipParams) (*http.Request, error) { + var err error - // GetUserInfoWithResponse request - GetUserInfoWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetUserInfoResponse, error) + var pathParam0 string - // ListRepositoryWithResponse request - ListRepositoryWithResponse(ctx context.Context, owner string, params *ListRepositoryParams, reqEditors ...RequestEditorFn) (*ListRepositoryResponse, error) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err + } - // GetVersionWithResponse request - GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) + var pathParam1 string - // DeleteWipWithResponse request - DeleteWipWithResponse(ctx context.Context, owner string, repository string, params *DeleteWipParams, reqEditors ...RequestEditorFn) (*DeleteWipResponse, error) + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } - // GetWipWithResponse request - GetWipWithResponse(ctx context.Context, owner string, repository string, params *GetWipParams, reqEditors ...RequestEditorFn) (*GetWipResponse, error) + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } - // GetWipChangesWithResponse request - GetWipChangesWithResponse(ctx context.Context, owner string, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*GetWipChangesResponse, error) + operationPath := fmt.Sprintf("/wip/%s/%s/commit", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } - // CommitWipWithResponse request - CommitWipWithResponse(ctx context.Context, owner string, repository string, params *CommitWipParams, reqEditors ...RequestEditorFn) (*CommitWipResponse, error) + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } - // ListWipWithResponse request - ListWipWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*ListWipResponse, error) + if params != nil { + queryValues := queryURL.Query() - // RevertWipChangesWithResponse request - RevertWipChangesWithResponse(ctx context.Context, owner string, repository string, params *RevertWipChangesParams, reqEditors ...RequestEditorFn) (*RevertWipChangesResponse, error) -} + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "msg", runtime.ParamLocationQuery, params.Msg); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } -type LoginResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *AuthenticationToken -} + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } -// Status returns HTTPResponse.Status -func (r LoginResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status + queryURL.RawQuery = queryValues.Encode() } - return http.StatusText(0) -} -// StatusCode returns HTTPResponse.StatusCode -func (r LoginResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + req, err := http.NewRequest("POST", queryURL.String(), nil) + if err != nil { + return nil, err } - return 0 -} -type LogoutResponse struct { - Body []byte - HTTPResponse *http.Response + return req, nil } -// Status returns HTTPResponse.Status -func (r LogoutResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} +// NewListWipRequest generates requests for ListWip +func NewListWipRequest(server string, owner string, repository string) (*http.Request, error) { + var err error -// StatusCode returns HTTPResponse.StatusCode -func (r LogoutResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err } - return 0 -} -type DeleteObjectResponse struct { - Body []byte - HTTPResponse *http.Response -} + var pathParam1 string -// Status returns HTTPResponse.Status -func (r DeleteObjectResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err } - return http.StatusText(0) -} -// StatusCode returns HTTPResponse.StatusCode -func (r DeleteObjectResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + serverURL, err := url.Parse(server) + if err != nil { + return nil, err } - return 0 -} -type GetObjectResponse struct { - Body []byte - HTTPResponse *http.Response -} + operationPath := fmt.Sprintf("/wip/%s/%s/list", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } -// Status returns HTTPResponse.Status -func (r GetObjectResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err } - return http.StatusText(0) -} -// StatusCode returns HTTPResponse.StatusCode -func (r GetObjectResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err } - return 0 -} -type HeadObjectResponse struct { - Body []byte - HTTPResponse *http.Response + return req, nil } -// Status returns HTTPResponse.Status -func (r HeadObjectResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} +// NewRevertWipChangesRequest generates requests for RevertWipChanges +func NewRevertWipChangesRequest(server string, owner string, repository string, params *RevertWipChangesParams) (*http.Request, error) { + var err error -// StatusCode returns HTTPResponse.StatusCode -func (r HeadObjectResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err } - return 0 -} -type UploadObjectResponse struct { - Body []byte - HTTPResponse *http.Response - JSON201 *ObjectStats + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/wip/%s/%s/revert", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + if params.PathPrefix != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "pathPrefix", runtime.ParamLocationQuery, *params.PathPrefix); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("POST", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil } -// Status returns HTTPResponse.Status -func (r UploadObjectResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } } - return http.StatusText(0) + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil } -// StatusCode returns HTTPResponse.StatusCode -func (r UploadObjectResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err } - return 0 + return &ClientWithResponses{client}, nil } -type DeleteRepositoryResponse struct { +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // LoginWithBodyWithResponse request with any body + LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) + + LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) + + // LogoutWithResponse request + LogoutWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*LogoutResponse, error) + + // ListMergeRequestsWithResponse request + ListMergeRequestsWithResponse(ctx context.Context, owner string, repository string, params *ListMergeRequestsParams, reqEditors ...RequestEditorFn) (*ListMergeRequestsResponse, error) + + // CreateMergeRequestWithBodyWithResponse request with any body + CreateMergeRequestWithBodyWithResponse(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateMergeRequestResponse, error) + + CreateMergeRequestWithResponse(ctx context.Context, owner string, repository string, body CreateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateMergeRequestResponse, error) + + // DeleteMergeRequestWithResponse request + DeleteMergeRequestWithResponse(ctx context.Context, owner string, repository string, mrId uint64, reqEditors ...RequestEditorFn) (*DeleteMergeRequestResponse, error) + + // GetMergeRequestWithResponse request + GetMergeRequestWithResponse(ctx context.Context, owner string, repository string, mrId uint64, reqEditors ...RequestEditorFn) (*GetMergeRequestResponse, error) + + // UpdateMergeRequestWithBodyWithResponse request with any body + UpdateMergeRequestWithBodyWithResponse(ctx context.Context, owner string, repository string, mrId uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateMergeRequestResponse, error) + + UpdateMergeRequestWithResponse(ctx context.Context, owner string, repository string, mrId uint64, body UpdateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateMergeRequestResponse, error) + + // DeleteObjectWithResponse request + DeleteObjectWithResponse(ctx context.Context, owner string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) + + // GetObjectWithResponse request + GetObjectWithResponse(ctx context.Context, owner string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*GetObjectResponse, error) + + // HeadObjectWithResponse request + HeadObjectWithResponse(ctx context.Context, owner string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*HeadObjectResponse, error) + + // UploadObjectWithBodyWithResponse request with any body + UploadObjectWithBodyWithResponse(ctx context.Context, owner string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) + + // DeleteRepositoryWithResponse request + DeleteRepositoryWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*DeleteRepositoryResponse, error) + + // GetRepositoryWithResponse request + GetRepositoryWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*GetRepositoryResponse, error) + + // UpdateRepositoryWithBodyWithResponse request with any body + UpdateRepositoryWithBodyWithResponse(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) + + UpdateRepositoryWithResponse(ctx context.Context, owner string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) + + // DeleteBranchWithResponse request + DeleteBranchWithResponse(ctx context.Context, owner string, repository string, params *DeleteBranchParams, reqEditors ...RequestEditorFn) (*DeleteBranchResponse, error) + + // GetBranchWithResponse request + GetBranchWithResponse(ctx context.Context, owner string, repository string, params *GetBranchParams, reqEditors ...RequestEditorFn) (*GetBranchResponse, error) + + // CreateBranchWithBodyWithResponse request with any body + CreateBranchWithBodyWithResponse(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) + + CreateBranchWithResponse(ctx context.Context, owner string, repository string, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) + + // ListBranchesWithResponse request + ListBranchesWithResponse(ctx context.Context, owner string, repository string, params *ListBranchesParams, reqEditors ...RequestEditorFn) (*ListBranchesResponse, error) + + // GetCommitChangesWithResponse request + GetCommitChangesWithResponse(ctx context.Context, owner string, repository string, commitId string, params *GetCommitChangesParams, reqEditors ...RequestEditorFn) (*GetCommitChangesResponse, error) + + // GetCommitsInRefWithResponse request + GetCommitsInRefWithResponse(ctx context.Context, owner string, repository string, params *GetCommitsInRefParams, reqEditors ...RequestEditorFn) (*GetCommitsInRefResponse, error) + + // CompareCommitWithResponse request + CompareCommitWithResponse(ctx context.Context, owner string, repository string, basehead string, params *CompareCommitParams, reqEditors ...RequestEditorFn) (*CompareCommitResponse, error) + + // GetEntriesInRefWithResponse request + GetEntriesInRefWithResponse(ctx context.Context, owner string, repository string, params *GetEntriesInRefParams, reqEditors ...RequestEditorFn) (*GetEntriesInRefResponse, error) + + // GetSetupStateWithResponse request + GetSetupStateWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetSetupStateResponse, error) + + // RegisterWithBodyWithResponse request with any body + RegisterWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterResponse, error) + + RegisterWithResponse(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterResponse, error) + + // ListRepositoryOfAuthenticatedUserWithResponse request + ListRepositoryOfAuthenticatedUserWithResponse(ctx context.Context, params *ListRepositoryOfAuthenticatedUserParams, reqEditors ...RequestEditorFn) (*ListRepositoryOfAuthenticatedUserResponse, error) + + // CreateRepositoryWithBodyWithResponse request with any body + CreateRepositoryWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateRepositoryResponse, error) + + CreateRepositoryWithResponse(ctx context.Context, body CreateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateRepositoryResponse, error) + + // GetUserInfoWithResponse request + GetUserInfoWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetUserInfoResponse, error) + + // ListRepositoryWithResponse request + ListRepositoryWithResponse(ctx context.Context, owner string, params *ListRepositoryParams, reqEditors ...RequestEditorFn) (*ListRepositoryResponse, error) + + // GetVersionWithResponse request + GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) + + // DeleteWipWithResponse request + DeleteWipWithResponse(ctx context.Context, owner string, repository string, params *DeleteWipParams, reqEditors ...RequestEditorFn) (*DeleteWipResponse, error) + + // GetWipWithResponse request + GetWipWithResponse(ctx context.Context, owner string, repository string, params *GetWipParams, reqEditors ...RequestEditorFn) (*GetWipResponse, error) + + // GetWipChangesWithResponse request + GetWipChangesWithResponse(ctx context.Context, owner string, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*GetWipChangesResponse, error) + + // CommitWipWithResponse request + CommitWipWithResponse(ctx context.Context, owner string, repository string, params *CommitWipParams, reqEditors ...RequestEditorFn) (*CommitWipResponse, error) + + // ListWipWithResponse request + ListWipWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*ListWipResponse, error) + + // RevertWipChangesWithResponse request + RevertWipChangesWithResponse(ctx context.Context, owner string, repository string, params *RevertWipChangesParams, reqEditors ...RequestEditorFn) (*RevertWipChangesResponse, error) +} + +type LoginResponse struct { Body []byte HTTPResponse *http.Response + JSON200 *AuthenticationToken } // Status returns HTTPResponse.Status -func (r DeleteRepositoryResponse) Status() string { +func (r LoginResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3226,21 +3580,20 @@ func (r DeleteRepositoryResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r DeleteRepositoryResponse) StatusCode() int { +func (r LoginResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetRepositoryResponse struct { +type LogoutResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *Repository } // Status returns HTTPResponse.Status -func (r GetRepositoryResponse) Status() string { +func (r LogoutResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3248,20 +3601,21 @@ func (r GetRepositoryResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetRepositoryResponse) StatusCode() int { +func (r LogoutResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type UpdateRepositoryResponse struct { +type ListMergeRequestsResponse struct { Body []byte HTTPResponse *http.Response + JSON200 *MergeRequestList } // Status returns HTTPResponse.Status -func (r UpdateRepositoryResponse) Status() string { +func (r ListMergeRequestsResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3269,20 +3623,21 @@ func (r UpdateRepositoryResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r UpdateRepositoryResponse) StatusCode() int { +func (r ListMergeRequestsResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type DeleteBranchResponse struct { +type CreateMergeRequestResponse struct { Body []byte HTTPResponse *http.Response + JSON201 *MergeRequest } // Status returns HTTPResponse.Status -func (r DeleteBranchResponse) Status() string { +func (r CreateMergeRequestResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3290,21 +3645,20 @@ func (r DeleteBranchResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r DeleteBranchResponse) StatusCode() int { +func (r CreateMergeRequestResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetBranchResponse struct { +type DeleteMergeRequestResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *Branch } // Status returns HTTPResponse.Status -func (r GetBranchResponse) Status() string { +func (r DeleteMergeRequestResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3312,21 +3666,21 @@ func (r GetBranchResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetBranchResponse) StatusCode() int { +func (r DeleteMergeRequestResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type CreateBranchResponse struct { +type GetMergeRequestResponse struct { Body []byte HTTPResponse *http.Response - JSON201 *BranchCreation + JSON200 *MergeRequest } // Status returns HTTPResponse.Status -func (r CreateBranchResponse) Status() string { +func (r GetMergeRequestResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3334,21 +3688,20 @@ func (r CreateBranchResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r CreateBranchResponse) StatusCode() int { +func (r GetMergeRequestResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type ListBranchesResponse struct { +type UpdateMergeRequestResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *BranchList } // Status returns HTTPResponse.Status -func (r ListBranchesResponse) Status() string { +func (r UpdateMergeRequestResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3356,21 +3709,20 @@ func (r ListBranchesResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r ListBranchesResponse) StatusCode() int { +func (r UpdateMergeRequestResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetCommitChangesResponse struct { +type DeleteObjectResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *[]Change } // Status returns HTTPResponse.Status -func (r GetCommitChangesResponse) Status() string { +func (r DeleteObjectResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3378,7 +3730,244 @@ func (r GetCommitChangesResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetCommitChangesResponse) StatusCode() int { +func (r DeleteObjectResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetObjectResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r GetObjectResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetObjectResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type HeadObjectResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r HeadObjectResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r HeadObjectResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type UploadObjectResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *ObjectStats +} + +// Status returns HTTPResponse.Status +func (r UploadObjectResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UploadObjectResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type DeleteRepositoryResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r DeleteRepositoryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DeleteRepositoryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetRepositoryResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Repository +} + +// Status returns HTTPResponse.Status +func (r GetRepositoryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetRepositoryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type UpdateRepositoryResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r UpdateRepositoryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UpdateRepositoryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type DeleteBranchResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r DeleteBranchResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DeleteBranchResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetBranchResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Branch +} + +// Status returns HTTPResponse.Status +func (r GetBranchResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetBranchResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type CreateBranchResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *BranchCreation +} + +// Status returns HTTPResponse.Status +func (r CreateBranchResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateBranchResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type ListBranchesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *BranchList +} + +// Status returns HTTPResponse.Status +func (r ListBranchesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ListBranchesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetCommitChangesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]Change +} + +// Status returns HTTPResponse.Status +func (r GetCommitChangesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetCommitChangesResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -3760,45 +4349,106 @@ func (c *ClientWithResponses) LogoutWithResponse(ctx context.Context, reqEditors return ParseLogoutResponse(rsp) } -// DeleteObjectWithResponse request returning *DeleteObjectResponse -func (c *ClientWithResponses) DeleteObjectWithResponse(ctx context.Context, owner string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) { - rsp, err := c.DeleteObject(ctx, owner, repository, params, reqEditors...) +// ListMergeRequestsWithResponse request returning *ListMergeRequestsResponse +func (c *ClientWithResponses) ListMergeRequestsWithResponse(ctx context.Context, owner string, repository string, params *ListMergeRequestsParams, reqEditors ...RequestEditorFn) (*ListMergeRequestsResponse, error) { + rsp, err := c.ListMergeRequests(ctx, owner, repository, params, reqEditors...) if err != nil { return nil, err } - return ParseDeleteObjectResponse(rsp) + return ParseListMergeRequestsResponse(rsp) } -// GetObjectWithResponse request returning *GetObjectResponse -func (c *ClientWithResponses) GetObjectWithResponse(ctx context.Context, owner string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*GetObjectResponse, error) { - rsp, err := c.GetObject(ctx, owner, repository, params, reqEditors...) +// CreateMergeRequestWithBodyWithResponse request with arbitrary body returning *CreateMergeRequestResponse +func (c *ClientWithResponses) CreateMergeRequestWithBodyWithResponse(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateMergeRequestResponse, error) { + rsp, err := c.CreateMergeRequestWithBody(ctx, owner, repository, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseGetObjectResponse(rsp) + return ParseCreateMergeRequestResponse(rsp) } -// HeadObjectWithResponse request returning *HeadObjectResponse -func (c *ClientWithResponses) HeadObjectWithResponse(ctx context.Context, owner string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*HeadObjectResponse, error) { - rsp, err := c.HeadObject(ctx, owner, repository, params, reqEditors...) +func (c *ClientWithResponses) CreateMergeRequestWithResponse(ctx context.Context, owner string, repository string, body CreateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateMergeRequestResponse, error) { + rsp, err := c.CreateMergeRequest(ctx, owner, repository, body, reqEditors...) if err != nil { return nil, err } - return ParseHeadObjectResponse(rsp) + return ParseCreateMergeRequestResponse(rsp) } -// UploadObjectWithBodyWithResponse request with arbitrary body returning *UploadObjectResponse -func (c *ClientWithResponses) UploadObjectWithBodyWithResponse(ctx context.Context, owner string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) { - rsp, err := c.UploadObjectWithBody(ctx, owner, repository, params, contentType, body, reqEditors...) +// DeleteMergeRequestWithResponse request returning *DeleteMergeRequestResponse +func (c *ClientWithResponses) DeleteMergeRequestWithResponse(ctx context.Context, owner string, repository string, mrId uint64, reqEditors ...RequestEditorFn) (*DeleteMergeRequestResponse, error) { + rsp, err := c.DeleteMergeRequest(ctx, owner, repository, mrId, reqEditors...) if err != nil { return nil, err } - return ParseUploadObjectResponse(rsp) + return ParseDeleteMergeRequestResponse(rsp) } -// DeleteRepositoryWithResponse request returning *DeleteRepositoryResponse -func (c *ClientWithResponses) DeleteRepositoryWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*DeleteRepositoryResponse, error) { - rsp, err := c.DeleteRepository(ctx, owner, repository, reqEditors...) +// GetMergeRequestWithResponse request returning *GetMergeRequestResponse +func (c *ClientWithResponses) GetMergeRequestWithResponse(ctx context.Context, owner string, repository string, mrId uint64, reqEditors ...RequestEditorFn) (*GetMergeRequestResponse, error) { + rsp, err := c.GetMergeRequest(ctx, owner, repository, mrId, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetMergeRequestResponse(rsp) +} + +// UpdateMergeRequestWithBodyWithResponse request with arbitrary body returning *UpdateMergeRequestResponse +func (c *ClientWithResponses) UpdateMergeRequestWithBodyWithResponse(ctx context.Context, owner string, repository string, mrId uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateMergeRequestResponse, error) { + rsp, err := c.UpdateMergeRequestWithBody(ctx, owner, repository, mrId, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateMergeRequestResponse(rsp) +} + +func (c *ClientWithResponses) UpdateMergeRequestWithResponse(ctx context.Context, owner string, repository string, mrId uint64, body UpdateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateMergeRequestResponse, error) { + rsp, err := c.UpdateMergeRequest(ctx, owner, repository, mrId, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateMergeRequestResponse(rsp) +} + +// DeleteObjectWithResponse request returning *DeleteObjectResponse +func (c *ClientWithResponses) DeleteObjectWithResponse(ctx context.Context, owner string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) { + rsp, err := c.DeleteObject(ctx, owner, repository, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteObjectResponse(rsp) +} + +// GetObjectWithResponse request returning *GetObjectResponse +func (c *ClientWithResponses) GetObjectWithResponse(ctx context.Context, owner string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*GetObjectResponse, error) { + rsp, err := c.GetObject(ctx, owner, repository, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetObjectResponse(rsp) +} + +// HeadObjectWithResponse request returning *HeadObjectResponse +func (c *ClientWithResponses) HeadObjectWithResponse(ctx context.Context, owner string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*HeadObjectResponse, error) { + rsp, err := c.HeadObject(ctx, owner, repository, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseHeadObjectResponse(rsp) +} + +// UploadObjectWithBodyWithResponse request with arbitrary body returning *UploadObjectResponse +func (c *ClientWithResponses) UploadObjectWithBodyWithResponse(ctx context.Context, owner string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) { + rsp, err := c.UploadObjectWithBody(ctx, owner, repository, params, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUploadObjectResponse(rsp) +} + +// DeleteRepositoryWithResponse request returning *DeleteRepositoryResponse +func (c *ClientWithResponses) DeleteRepositoryWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*DeleteRepositoryResponse, error) { + rsp, err := c.DeleteRepository(ctx, owner, repository, reqEditors...) if err != nil { return nil, err } @@ -4086,6 +4736,116 @@ func ParseLogoutResponse(rsp *http.Response) (*LogoutResponse, error) { return response, nil } +// ParseListMergeRequestsResponse parses an HTTP response from a ListMergeRequestsWithResponse call +func ParseListMergeRequestsResponse(rsp *http.Response) (*ListMergeRequestsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListMergeRequestsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest MergeRequestList + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseCreateMergeRequestResponse parses an HTTP response from a CreateMergeRequestWithResponse call +func ParseCreateMergeRequestResponse(rsp *http.Response) (*CreateMergeRequestResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateMergeRequestResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest MergeRequest + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil +} + +// ParseDeleteMergeRequestResponse parses an HTTP response from a DeleteMergeRequestWithResponse call +func ParseDeleteMergeRequestResponse(rsp *http.Response) (*DeleteMergeRequestResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DeleteMergeRequestResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetMergeRequestResponse parses an HTTP response from a GetMergeRequestWithResponse call +func ParseGetMergeRequestResponse(rsp *http.Response) (*GetMergeRequestResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetMergeRequestResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest MergeRequest + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseUpdateMergeRequestResponse parses an HTTP response from a UpdateMergeRequestWithResponse call +func ParseUpdateMergeRequestResponse(rsp *http.Response) (*UpdateMergeRequestResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UpdateMergeRequestResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + // ParseDeleteObjectResponse parses an HTTP response from a DeleteObjectWithResponse call func ParseDeleteObjectResponse(rsp *http.Response) (*DeleteObjectResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -4732,6 +5492,21 @@ type ServerInterface interface { // perform a logout // (POST /auth/logout) Logout(ctx context.Context, w *JiaozifsResponse, r *http.Request) + // get list of merge request in repository + // (GET /mergequest/{owner}/{repository}) + ListMergeRequests(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params ListMergeRequestsParams) + // create merge request + // (POST /mergequest/{owner}/{repository}) + CreateMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateMergeRequestJSONRequestBody, owner string, repository string) + // delete merge request + // (DELETE /mergequest/{owner}/{repository}/mr/{mrId}) + DeleteMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, mrId uint64) + // get merge request + // (GET /mergequest/{owner}/{repository}/mr/{mrId}) + GetMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, mrId uint64) + // update merge request + // (POST /mergequest/{owner}/{repository}/mr/{mrId}) + UpdateMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateMergeRequestJSONRequestBody, owner string, repository string, mrId uint64) // delete object. Missing objects will not return a NotFound error. // (DELETE /object/{owner}/{repository}) DeleteObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteObjectParams) @@ -4828,216 +5603,525 @@ func (_ Unimplemented) Login(ctx context.Context, w *JiaozifsResponse, r *http.R w.WriteHeader(http.StatusNotImplemented) } -// perform a logout -// (POST /auth/logout) -func (_ Unimplemented) Logout(ctx context.Context, w *JiaozifsResponse, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) -} +// perform a logout +// (POST /auth/logout) +func (_ Unimplemented) Logout(ctx context.Context, w *JiaozifsResponse, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get list of merge request in repository +// (GET /mergequest/{owner}/{repository}) +func (_ Unimplemented) ListMergeRequests(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params ListMergeRequestsParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// create merge request +// (POST /mergequest/{owner}/{repository}) +func (_ Unimplemented) CreateMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateMergeRequestJSONRequestBody, owner string, repository string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// delete merge request +// (DELETE /mergequest/{owner}/{repository}/mr/{mrId}) +func (_ Unimplemented) DeleteMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, mrId uint64) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get merge request +// (GET /mergequest/{owner}/{repository}/mr/{mrId}) +func (_ Unimplemented) GetMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, mrId uint64) { + w.WriteHeader(http.StatusNotImplemented) +} + +// update merge request +// (POST /mergequest/{owner}/{repository}/mr/{mrId}) +func (_ Unimplemented) UpdateMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateMergeRequestJSONRequestBody, owner string, repository string, mrId uint64) { + w.WriteHeader(http.StatusNotImplemented) +} + +// delete object. Missing objects will not return a NotFound error. +// (DELETE /object/{owner}/{repository}) +func (_ Unimplemented) DeleteObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteObjectParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get object content +// (GET /object/{owner}/{repository}) +func (_ Unimplemented) GetObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetObjectParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// check if object exists +// (HEAD /object/{owner}/{repository}) +func (_ Unimplemented) HeadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params HeadObjectParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// (POST /object/{owner}/{repository}) +func (_ Unimplemented) UploadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params UploadObjectParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// delete repository +// (DELETE /repos/{owner}/{repository}) +func (_ Unimplemented) DeleteRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get repository +// (GET /repos/{owner}/{repository}) +func (_ Unimplemented) GetRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// update repository +// (POST /repos/{owner}/{repository}) +func (_ Unimplemented) UpdateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateRepositoryJSONRequestBody, owner string, repository string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// delete branch +// (DELETE /repos/{owner}/{repository}/branch) +func (_ Unimplemented) DeleteBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteBranchParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get branch +// (GET /repos/{owner}/{repository}/branch) +func (_ Unimplemented) GetBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetBranchParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// create branch +// (POST /repos/{owner}/{repository}/branch) +func (_ Unimplemented) CreateBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateBranchJSONRequestBody, owner string, repository string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// list branches +// (GET /repos/{owner}/{repository}/branches) +func (_ Unimplemented) ListBranches(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params ListBranchesParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get changes in commit +// (GET /repos/{owner}/{repository}/changes/{commit_id}) +func (_ Unimplemented) GetCommitChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, commitId string, params GetCommitChangesParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get commits in ref +// (GET /repos/{owner}/{repository}/commits) +func (_ Unimplemented) GetCommitsInRef(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetCommitsInRefParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// compare two commit +// (GET /repos/{owner}/{repository}/compare/{basehead}) +func (_ Unimplemented) CompareCommit(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, basehead string, params CompareCommitParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// list entries in ref +// (GET /repos/{owner}/{repository}/contents) +func (_ Unimplemented) GetEntriesInRef(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetEntriesInRefParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// check if jiaozifs setup +// (GET /setup) +func (_ Unimplemented) GetSetupState(ctx context.Context, w *JiaozifsResponse, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// perform user registration +// (POST /users/register) +func (_ Unimplemented) Register(ctx context.Context, w *JiaozifsResponse, r *http.Request, body RegisterJSONRequestBody) { + w.WriteHeader(http.StatusNotImplemented) +} + +// list repository +// (GET /users/repos) +func (_ Unimplemented) ListRepositoryOfAuthenticatedUser(ctx context.Context, w *JiaozifsResponse, r *http.Request, params ListRepositoryOfAuthenticatedUserParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// create repository +// (POST /users/repos) +func (_ Unimplemented) CreateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateRepositoryJSONRequestBody) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get information of the currently logged-in user +// (GET /users/user) +func (_ Unimplemented) GetUserInfo(ctx context.Context, w *JiaozifsResponse, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// list repository in specific owner +// (GET /users/{owner}/repos) +func (_ Unimplemented) ListRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, params ListRepositoryParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// return program and runtime version +// (GET /version) +func (_ Unimplemented) GetVersion(ctx context.Context, w *JiaozifsResponse, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// remove working in process +// (DELETE /wip/{owner}/{repository}) +func (_ Unimplemented) DeleteWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteWipParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get working in process +// (GET /wip/{owner}/{repository}) +func (_ Unimplemented) GetWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetWipParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get working in process changes +// (GET /wip/{owner}/{repository}/changes) +func (_ Unimplemented) GetWipChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetWipChangesParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// commit working in process to branch +// (POST /wip/{owner}/{repository}/commit) +func (_ Unimplemented) CommitWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params CommitWipParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// list wip in specific project and user +// (GET /wip/{owner}/{repository}/list) +func (_ Unimplemented) ListWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// revert changes in working in process, empty path will revert all +// (POST /wip/{owner}/{repository}/revert) +func (_ Unimplemented) RevertWipChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params RevertWipChangesParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// Login operation middleware +func (siw *ServerInterfaceWrapper) Login(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + // ------------- Body parse ------------- + var body LoginJSONRequestBody + parseBody := r.ContentLength != 0 + if parseBody { + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "Error unmarshalling body 'Login' as JSON", http.StatusBadRequest) + return + } + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.Login(r.Context(), &JiaozifsResponse{w}, r, body) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// Logout operation middleware +func (siw *ServerInterfaceWrapper) Logout(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.Logout(r.Context(), &JiaozifsResponse{w}, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// ListMergeRequests operation middleware +func (siw *ServerInterfaceWrapper) ListMergeRequests(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params ListMergeRequestsParams + + // ------------- Optional query parameter "prefix" ------------- + + err = runtime.BindQueryParameter("form", true, false, "prefix", r.URL.Query(), ¶ms.Prefix) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "prefix", Err: err}) + return + } + + // ------------- Optional query parameter "after" ------------- + + err = runtime.BindQueryParameter("form", true, false, "after", r.URL.Query(), ¶ms.After) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "after", Err: err}) + return + } + + // ------------- Optional query parameter "amount" ------------- + + err = runtime.BindQueryParameter("form", true, false, "amount", r.URL.Query(), ¶ms.Amount) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "amount", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListMergeRequests(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// CreateMergeRequest operation middleware +func (siw *ServerInterfaceWrapper) CreateMergeRequest(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Body parse ------------- + var body CreateMergeRequestJSONRequestBody + parseBody := true + if parseBody { + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "Error unmarshalling body 'CreateMergeRequest' as JSON", http.StatusBadRequest) + return + } + } + + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CreateMergeRequest(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } -// delete object. Missing objects will not return a NotFound error. -// (DELETE /object/{owner}/{repository}) -func (_ Unimplemented) DeleteObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteObjectParams) { - w.WriteHeader(http.StatusNotImplemented) + handler.ServeHTTP(w, r.WithContext(ctx)) } -// get object content -// (GET /object/{owner}/{repository}) -func (_ Unimplemented) GetObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetObjectParams) { - w.WriteHeader(http.StatusNotImplemented) -} +// DeleteMergeRequest operation middleware +func (siw *ServerInterfaceWrapper) DeleteMergeRequest(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() -// check if object exists -// (HEAD /object/{owner}/{repository}) -func (_ Unimplemented) HeadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params HeadObjectParams) { - w.WriteHeader(http.StatusNotImplemented) -} + var err error -// (POST /object/{owner}/{repository}) -func (_ Unimplemented) UploadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params UploadObjectParams) { - w.WriteHeader(http.StatusNotImplemented) -} + // ------------- Path parameter "owner" ------------- + var owner string -// delete repository -// (DELETE /repos/{owner}/{repository}) -func (_ Unimplemented) DeleteRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) { - w.WriteHeader(http.StatusNotImplemented) -} + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } -// get repository -// (GET /repos/{owner}/{repository}) -func (_ Unimplemented) GetRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) { - w.WriteHeader(http.StatusNotImplemented) -} + // ------------- Path parameter "repository" ------------- + var repository string -// update repository -// (POST /repos/{owner}/{repository}) -func (_ Unimplemented) UpdateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateRepositoryJSONRequestBody, owner string, repository string) { - w.WriteHeader(http.StatusNotImplemented) -} + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } -// delete branch -// (DELETE /repos/{owner}/{repository}/branch) -func (_ Unimplemented) DeleteBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteBranchParams) { - w.WriteHeader(http.StatusNotImplemented) -} + // ------------- Path parameter "mrId" ------------- + var mrId uint64 -// get branch -// (GET /repos/{owner}/{repository}/branch) -func (_ Unimplemented) GetBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetBranchParams) { - w.WriteHeader(http.StatusNotImplemented) -} + err = runtime.BindStyledParameterWithOptions("simple", "mrId", chi.URLParam(r, "mrId"), &mrId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "mrId", Err: err}) + return + } -// create branch -// (POST /repos/{owner}/{repository}/branch) -func (_ Unimplemented) CreateBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateBranchJSONRequestBody, owner string, repository string) { - w.WriteHeader(http.StatusNotImplemented) -} + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) -// list branches -// (GET /repos/{owner}/{repository}/branches) -func (_ Unimplemented) ListBranches(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params ListBranchesParams) { - w.WriteHeader(http.StatusNotImplemented) -} + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) -// get changes in commit -// (GET /repos/{owner}/{repository}/changes/{commit_id}) -func (_ Unimplemented) GetCommitChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, commitId string, params GetCommitChangesParams) { - w.WriteHeader(http.StatusNotImplemented) -} + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) -// get commits in ref -// (GET /repos/{owner}/{repository}/commits) -func (_ Unimplemented) GetCommitsInRef(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetCommitsInRefParams) { - w.WriteHeader(http.StatusNotImplemented) -} + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteMergeRequest(r.Context(), &JiaozifsResponse{w}, r, owner, repository, mrId) + })) -// compare two commit -// (GET /repos/{owner}/{repository}/compare/{basehead}) -func (_ Unimplemented) CompareCommit(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, basehead string, params CompareCommitParams) { - w.WriteHeader(http.StatusNotImplemented) -} + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } -// list entries in ref -// (GET /repos/{owner}/{repository}/contents) -func (_ Unimplemented) GetEntriesInRef(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetEntriesInRefParams) { - w.WriteHeader(http.StatusNotImplemented) + handler.ServeHTTP(w, r.WithContext(ctx)) } -// check if jiaozifs setup -// (GET /setup) -func (_ Unimplemented) GetSetupState(ctx context.Context, w *JiaozifsResponse, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) -} +// GetMergeRequest operation middleware +func (siw *ServerInterfaceWrapper) GetMergeRequest(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() -// perform user registration -// (POST /users/register) -func (_ Unimplemented) Register(ctx context.Context, w *JiaozifsResponse, r *http.Request, body RegisterJSONRequestBody) { - w.WriteHeader(http.StatusNotImplemented) -} + var err error -// list repository -// (GET /users/repos) -func (_ Unimplemented) ListRepositoryOfAuthenticatedUser(ctx context.Context, w *JiaozifsResponse, r *http.Request, params ListRepositoryOfAuthenticatedUserParams) { - w.WriteHeader(http.StatusNotImplemented) -} + // ------------- Path parameter "owner" ------------- + var owner string -// create repository -// (POST /users/repos) -func (_ Unimplemented) CreateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateRepositoryJSONRequestBody) { - w.WriteHeader(http.StatusNotImplemented) -} + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } -// get information of the currently logged-in user -// (GET /users/user) -func (_ Unimplemented) GetUserInfo(ctx context.Context, w *JiaozifsResponse, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) -} + // ------------- Path parameter "repository" ------------- + var repository string -// list repository in specific owner -// (GET /users/{owner}/repos) -func (_ Unimplemented) ListRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, params ListRepositoryParams) { - w.WriteHeader(http.StatusNotImplemented) -} + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } -// return program and runtime version -// (GET /version) -func (_ Unimplemented) GetVersion(ctx context.Context, w *JiaozifsResponse, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) -} + // ------------- Path parameter "mrId" ------------- + var mrId uint64 -// remove working in process -// (DELETE /wip/{owner}/{repository}) -func (_ Unimplemented) DeleteWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteWipParams) { - w.WriteHeader(http.StatusNotImplemented) -} + err = runtime.BindStyledParameterWithOptions("simple", "mrId", chi.URLParam(r, "mrId"), &mrId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "mrId", Err: err}) + return + } -// get working in process -// (GET /wip/{owner}/{repository}) -func (_ Unimplemented) GetWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetWipParams) { - w.WriteHeader(http.StatusNotImplemented) -} + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) -// get working in process changes -// (GET /wip/{owner}/{repository}/changes) -func (_ Unimplemented) GetWipChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetWipChangesParams) { - w.WriteHeader(http.StatusNotImplemented) -} + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) -// commit working in process to branch -// (POST /wip/{owner}/{repository}/commit) -func (_ Unimplemented) CommitWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params CommitWipParams) { - w.WriteHeader(http.StatusNotImplemented) -} + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) -// list wip in specific project and user -// (GET /wip/{owner}/{repository}/list) -func (_ Unimplemented) ListWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) { - w.WriteHeader(http.StatusNotImplemented) -} + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetMergeRequest(r.Context(), &JiaozifsResponse{w}, r, owner, repository, mrId) + })) -// revert changes in working in process, empty path will revert all -// (POST /wip/{owner}/{repository}/revert) -func (_ Unimplemented) RevertWipChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params RevertWipChangesParams) { - w.WriteHeader(http.StatusNotImplemented) -} + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } -// ServerInterfaceWrapper converts contexts to parameters. -type ServerInterfaceWrapper struct { - Handler ServerInterface - HandlerMiddlewares []MiddlewareFunc - ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + handler.ServeHTTP(w, r.WithContext(ctx)) } -type MiddlewareFunc func(http.Handler) http.Handler - -// Login operation middleware -func (siw *ServerInterfaceWrapper) Login(w http.ResponseWriter, r *http.Request) { +// UpdateMergeRequest operation middleware +func (siw *ServerInterfaceWrapper) UpdateMergeRequest(w http.ResponseWriter, r *http.Request) { ctx := r.Context() + var err error + // ------------- Body parse ------------- - var body LoginJSONRequestBody - parseBody := r.ContentLength != 0 + var body UpdateMergeRequestJSONRequestBody + parseBody := true if parseBody { if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - http.Error(w, "Error unmarshalling body 'Login' as JSON", http.StatusBadRequest) + http.Error(w, "Error unmarshalling body 'UpdateMergeRequest' as JSON", http.StatusBadRequest) return } } - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.Login(r.Context(), &JiaozifsResponse{w}, r, body) - })) + // ------------- Path parameter "owner" ------------- + var owner string - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return } - handler.ServeHTTP(w, r.WithContext(ctx)) -} + // ------------- Path parameter "repository" ------------- + var repository string -// Logout operation middleware -func (siw *ServerInterfaceWrapper) Logout(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + // ------------- Path parameter "mrId" ------------- + var mrId uint64 + + err = runtime.BindStyledParameterWithOptions("simple", "mrId", chi.URLParam(r, "mrId"), &mrId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "mrId", Err: err}) + return + } ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) @@ -5046,7 +6130,7 @@ func (siw *ServerInterfaceWrapper) Logout(w http.ResponseWriter, r *http.Request ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.Logout(r.Context(), &JiaozifsResponse{w}, r) + siw.Handler.UpdateMergeRequest(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository, mrId) })) for _, middleware := range siw.HandlerMiddlewares { @@ -6766,6 +7850,21 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/auth/logout", wrapper.Logout) }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/mergequest/{owner}/{repository}", wrapper.ListMergeRequests) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/mergequest/{owner}/{repository}", wrapper.CreateMergeRequest) + }) + r.Group(func(r chi.Router) { + r.Delete(options.BaseURL+"/mergequest/{owner}/{repository}/mr/{mrId}", wrapper.DeleteMergeRequest) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/mergequest/{owner}/{repository}/mr/{mrId}", wrapper.GetMergeRequest) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/mergequest/{owner}/{repository}/mr/{mrId}", wrapper.UpdateMergeRequest) + }) r.Group(func(r chi.Router) { r.Delete(options.BaseURL+"/object/{owner}/{repository}", wrapper.DeleteObject) }) @@ -6857,77 +7956,83 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "/y6wKrB1fQAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 2eda877f..b4174bf7 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -140,6 +140,87 @@ components: RefType: type: string enum: ["branch", "wip","tag", "commit"] + CreateMergeRequest: + type: object + required: + - target_branch_name + - source_branch_name + - title + properties: + target_branch_name: + type: string + source_branch_name: + type: string + title: + type: string + maximum: 50 + description: + type: string + maximum: 500 + UpdateMergeRequest: + type: object + properties: + title: + type: string + description: + type: string + MergeRequest: + type: object + required: + - id + - target_branch + - source_branch + - source_repo_id + - target_repo_id + - title + - merge_status + - author_id + - created_at + - updated_at + properties: + id: + type: integer + format: uint64 + target_branch: + type: string + format: uuid + source_branch: + type: string + format: uuid + source_repo_id: + type: string + format: uuid + target_repo_id: + type: string + format: uuid + title: + type: string + merge_status: + type: integer + format: int + description: + type: string + author_id: + type: string + format: uuid + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + MergeRequestList: + type: object + required: + - pagination + - results + properties: + pagination: + $ref: "#/components/schemas/Pagination" + results: + type: array + items: + $ref: "#/components/schemas/MergeRequest" Branch: type: object required: @@ -1448,6 +1529,147 @@ paths: 403: description: Forbidden + /mergequest/{owner}/{repository}: + parameters: + - in: path + name: owner + required: true + schema: + type: string + - in: path + name: repository + required: true + schema: + type: string + get: + tags: + - mergerequest + operationId: listMergeRequests + summary: get list of merge request in repository + parameters: + - $ref: "#/components/parameters/PaginationPrefix" + - $ref: "#/components/parameters/PaginationRepoAfter" + - $ref: "#/components/parameters/PaginationAmount" + responses: + 200: + description: merge request + content: + application/json: + schema: + $ref: "#/components/schemas/MergeRequestList" + 401: + description: Unauthorized + 404: + description: Resource Not Found + 420: + description: Too many requests + 500: + description: Internal Server Error + post: + tags: + - mergerequest + operationId: createMergeRequest + summary: create merge request + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/CreateMergeRequest" + responses: + 201: + description: merge request + content: + application/json: + schema: + $ref: "#/components/schemas/MergeRequest" + 401: + description: Unauthorized + 404: + description: Resource Not Found + 420: + description: Too many requests + 500: + description: Internal Server Error + + /mergequest/{owner}/{repository}/mr/{mrId}: + parameters: + - in: path + name: owner + required: true + schema: + type: string + - in: path + name: repository + required: true + schema: + type: string + - in: path + name: mrId + required: true + schema: + type: integer + format: uint64 + get: + tags: + - mergerequest + operationId: getMergeRequest + summary: get merge request + responses: + 200: + description: merge request + content: + application/json: + schema: + $ref: "#/components/schemas/MergeRequest" + 401: + description: Unauthorized + 404: + description: Resource Not Found + 420: + description: Too many requests + 500: + description: Internal Server Error + delete: + tags: + - mergerequest + operationId: deleteMergeRequest + summary: delete merge request + responses: + 204: + description: delete merge request successfully + 401: + description: Unauthorized + 404: + description: Resource Not Found + 420: + description: Too many requests + 500: + description: Internal Server Error + post: + tags: + - mergerequest + operationId: updateMergeRequest + summary: update merge request + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/UpdateMergeRequest" + responses: + 200: + description: update merge request success + 401: + description: Unauthorized + 404: + description: Resource Not Found + 420: + description: Too many requests + 500: + description: Internal Server Error + + /users/{owner}/repos: parameters: - in: path diff --git a/controller/merge_request_ctl.go b/controller/merge_request_ctl.go new file mode 100644 index 00000000..0b7f8812 --- /dev/null +++ b/controller/merge_request_ctl.go @@ -0,0 +1,119 @@ +package controller + +import ( + "context" + "net/http" + "time" + + "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/auth" + "github.com/jiaozifs/jiaozifs/block/params" + "github.com/jiaozifs/jiaozifs/models" + "go.uber.org/fx" +) + +type MergeRequestController struct { + fx.In + + Repo models.IRepo + PublicStorageConfig params.AdapterConfig +} + +func (mrCtl MergeRequestController) ListMergeRequests(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, ownerName string, repositoryName string, params api.ListMergeRequestsParams) { + //TODO implement me + panic("implement me") +} + +func (mrCtl MergeRequestController) CreateMergeRequest(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.CreateMergeRequestJSONRequestBody, ownerName string, repositoryName string) { + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return + } + + owner, err := mrCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + if operator.Name != owner.Name { + w.Forbidden() + return + } + + // Get repo + repository, err := mrCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) + if err != nil { + w.Error(err) + return + } + + sorceBranch, err := mrCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(body.SourceBranchName)) + if err != nil { + w.Error(err) + return + } + + targetBranch, err := mrCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(body.TargetBranchName)) + if err != nil { + w.Error(err) + return + } + + mrModel, err := mrCtl.Repo.MergeRequestRepo().Insert(ctx, &models.MergeRequest{ + TargetBranch: targetBranch.ID, + SourceBranch: sorceBranch.ID, + SourceRepoID: repository.ID, + TargetRepoID: repository.ID, + Title: body.Title, + MergeStatus: models.InitMergeStatus, + Description: body.Description, + AuthorID: operator.ID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + + if err != nil { + w.Error(err) + return + } + //get merge state + w.JSON(mrModel, http.StatusCreated) +} + +func (mrCtl MergeRequestController) DeleteMergeRequest(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, ownerName string, repositoryName string, mrID uint64) { + //TODO implement me + panic("implement me") +} + +func (mrCtl MergeRequestController) GetMergeRequest(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, ownerName string, repositoryName string, mrID uint64) { + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return + } + + owner, err := mrCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + if operator.Name != owner.Name { + w.Forbidden() + return + } + + // Get repo + repository, err := mrCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) + if err != nil { + w.Error(err) + return + } +} + +func (mrCtl MergeRequestController) UpdateMergeRequest(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, body api.UpdateMergeRequestJSONRequestBody, ownerName string, repositoryName string, mrID uint64) { + //TODO implement me + panic("implement me") +} diff --git a/models/merge_request.go b/models/merge_request.go index 4f153324..9dbc5f96 100644 --- a/models/merge_request.go +++ b/models/merge_request.go @@ -10,11 +10,15 @@ import ( type MergeStatus int +const ( + InitMergeStatus MergeStatus = 1 +) + type MergeRequest struct { bun.BaseModel `bun:"table:merge_requests"` - ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()" json:"id"` - TargetBranch string `bun:"target_branch,notnull" json:"target_branch"` - SourceBranch string `bun:"source_branch,notnull" json:"source_branch"` + ID uint64 `bun:"id,pk,autoincrement" json:"id"` + TargetBranch uuid.UUID `bun:"target_branch,type:bytea,notnull" json:"target_branch"` + SourceBranch uuid.UUID `bun:"source_branch,type:bytea,notnull" json:"source_branch"` SourceRepoID uuid.UUID `bun:"source_repo_id,type:bytea,notnull" json:"source_repo_id"` TargetRepoID uuid.UUID `bun:"target_repo_id,type:bytea,notnull" json:"target_repo_id"` Title string `bun:"title,notnull" json:"title"` @@ -23,24 +27,20 @@ type MergeRequest struct { AuthorID uuid.UUID `bun:"author_id,type:bytea,notnull" json:"author_id"` - AssigneeID uuid.UUID `bun:"assignee_id,type:bytea" json:"assignee_id"` - MergeUserID uuid.UUID `bun:"merge_user_id,type:bytea" json:"merge_user_id"` - ApprovalsBeforeMerge int `bun:"approvals_before_merge" json:"approvals_before_merge"` - CreatedAt time.Time `bun:"created_at,notnull" json:"created_at"` UpdatedAt time.Time `bun:"updated_at,notnull" json:"updated_at"` } type GetMergeRequestParams struct { - ID uuid.UUID + ID *uint64 } func NewGetMergeRequestParams() *GetMergeRequestParams { return &GetMergeRequestParams{} } -func (gmr *GetMergeRequestParams) SetID(id uuid.UUID) *GetMergeRequestParams { - gmr.ID = id +func (gmr *GetMergeRequestParams) SetID(id uint64) *GetMergeRequestParams { + gmr.ID = &id return gmr } @@ -71,7 +71,7 @@ func (m MergeRequestRepo) Get(ctx context.Context, params *GetMergeRequestParams mergeRequest := &MergeRequest{} query := m.db.NewSelect().Model(mergeRequest) - if uuid.Nil != params.ID { + if params.ID != nil { query = query.Where("id = ?", params.ID) } diff --git a/models/repo.go b/models/repo.go index 385704f0..6a6bbafa 100644 --- a/models/repo.go +++ b/models/repo.go @@ -28,6 +28,7 @@ func IsolationLevelOption(level sql.IsolationLevel) TxOption { type IRepo interface { Transaction(ctx context.Context, fn func(repo IRepo) error, opts ...TxOption) error UserRepo() IUserRepo + MergeRequestRepo() IMergeRequestRepo FileTreeRepo(repoID uuid.UUID) IFileTreeRepo CommitRepo(repoID uuid.UUID) ICommitRepo TagRepo(repoID uuid.UUID) ITagRepo @@ -60,6 +61,14 @@ func (repo *PgRepo) UserRepo() IUserRepo { return NewUserRepo(repo.db) } +// MergeRequestRepo returns an instance of the IMergeRequestRepo interface. +// +// It does not take any parameters. +// It returns an IMergeRequestRepo. +func (repo *PgRepo) MergeRequestRepo() IMergeRequestRepo { + return NewMergeRequestRepo(repo.db) +} + func (repo *PgRepo) FileTreeRepo(repoID uuid.UUID) IFileTreeRepo { return NewFileTree(repo.db, repoID) } diff --git a/versionmgr/changes.go b/versionmgr/changes.go index c494b97a..c1c9f075 100644 --- a/versionmgr/changes.go +++ b/versionmgr/changes.go @@ -125,55 +125,30 @@ func newChanges(mChanges merkletrie.Changes) *Changes { return NewChanges(changes) } -// ConflictResolver resolve conflict between two change -type ConflictResolver func(base IChange, merged IChange) (IChange, error) - -// LeastHashResolve use least hash change for test -func LeastHashResolve(base IChange, merged IChange) (IChange, error) { - baseAction, err := base.Action() - if err != nil { - return nil, err - } - - mergeAction, err := merged.Action() - if err != nil { - return nil, err - } - - if baseAction == merkletrie.Delete { - return merged, nil - } - if mergeAction == merkletrie.Delete { - return base, nil - } - - if bytes.Compare(base.To().Hash(), merged.To().Hash()) < 0 { - return base, nil - } - return merged, nil +type ChangePair struct { + Base IChange + Merge IChange + IsConflict bool } -// ChangesMergeIter walk two changes set and try to resolve -type ChangesMergeIter struct { - baseChanges *Changes - mergerChanges *Changes - resolver ConflictResolver +type ChangesPairIter struct { + BaseChanges *Changes + MergerChanges *Changes } -// NewChangesMergeIter create a changes iter with two changes set and resolver function -func NewChangesMergeIter(baseChanges *Changes, mergerChanges *Changes, resolver ConflictResolver) *ChangesMergeIter { - return &ChangesMergeIter{baseChanges: baseChanges, mergerChanges: mergerChanges, resolver: resolver} +func NewChangesPairIter(baseChanges *Changes, mergerChanges *Changes) *ChangesPairIter { + return &ChangesPairIter{BaseChanges: baseChanges, MergerChanges: mergerChanges} } // Has check if any changes -func (cw *ChangesMergeIter) Has() bool { - return cw.baseChanges.Has() || cw.mergerChanges.Has() +func (cw *ChangesPairIter) Has() bool { + return cw.BaseChanges.Has() || cw.MergerChanges.Has() } // Reset reset changes -func (cw *ChangesMergeIter) Reset() { - cw.baseChanges.Reset() - cw.mergerChanges.Reset() +func (cw *ChangesPairIter) Reset() { + cw.BaseChanges.Reset() + cw.MergerChanges.Reset() } // Next find change file, first sort each file in change, pop files from two changes, compare filename, @@ -182,13 +157,13 @@ func (cw *ChangesMergeIter) Reset() { // base file > merge file, pop merge file and put base file back to queue // both file name match, try to resolve file changes // if one of the queue consume up, pick left change in the other queue -func (cw *ChangesMergeIter) Next() (IChange, error) { - baseNode, baseErr := cw.baseChanges.Next() +func (cw *ChangesPairIter) Next() (*ChangePair, error) { + baseNode, baseErr := cw.BaseChanges.Next() if baseErr != nil && baseErr != io.EOF { return nil, baseErr } - mergeNode, mergerError := cw.mergerChanges.Next() + mergeNode, mergerError := cw.MergerChanges.Next() if mergerError != nil && mergerError != io.EOF { return nil, mergerError } @@ -198,72 +173,126 @@ func (cw *ChangesMergeIter) Next() (IChange, error) { } if baseErr == io.EOF { - return mergeNode, nil + return &ChangePair{Merge: mergeNode}, nil } if mergerError == io.EOF { - return baseNode, nil + return &ChangePair{Base: baseNode}, nil } compare := strings.Compare(baseNode.Path(), mergeNode.Path()) if compare > 0 { //only merger change - cw.baseChanges.Back() - return mergeNode, nil + cw.BaseChanges.Back() + return &ChangePair{ + Merge: mergeNode, + }, nil } else if compare == 0 { - return cw.compareBothChange(baseNode, mergeNode) + isConflict, err := cw.isConflict(baseNode, mergeNode) + if err != nil { + return nil, err + } + return &ChangePair{ + Base: baseNode, + Merge: mergeNode, + IsConflict: isConflict, + }, nil } //only base change - cw.mergerChanges.Back() - return baseNode, nil + cw.MergerChanges.Back() + return &ChangePair{ + Base: baseNode, + }, nil } -func (cw *ChangesMergeIter) compareBothChange(base, merge IChange) (IChange, error) { +func (cw *ChangesPairIter) isConflict(base, merge IChange) (bool, error) { baseAction, err := base.Action() if err != nil { - return nil, err + return false, err } mergeAction, err := merge.Action() if err != nil { - return nil, err + return false, err } switch baseAction { case merkletrie.Insert: switch mergeAction { case merkletrie.Delete: - return cw.resolveConflict(base, merge) + return false, fmt.Errorf("%s merge should never be Delete while the base diff is Insert, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues %w", base.Path(), ErrActionNotMatch) case merkletrie.Modify: - return nil, fmt.Errorf("%s merge should never be Modify while the other diff is Insert, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues %w", base.Path(), ErrActionNotMatch) + return false, fmt.Errorf("%s merge should never be Modify while the base diff is Insert, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues %w", base.Path(), ErrActionNotMatch) case merkletrie.Insert: if bytes.Equal(base.To().Hash(), merge.To().Hash()) { - return base, nil + return false, nil } - return cw.resolveConflict(base, merge) + return true, nil } case merkletrie.Delete: switch mergeAction { case merkletrie.Delete: - return base, nil + return false, nil case merkletrie.Insert: - return nil, fmt.Errorf("%s merge should never be Insert while the other diff is Delete, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues %w", base.Path(), ErrActionNotMatch) + return false, fmt.Errorf("%s merge should never be Insert while the other diff is Delete, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues %w", base.Path(), ErrActionNotMatch) case merkletrie.Modify: - return cw.resolveConflict(base, merge) + return true, nil } case merkletrie.Modify: switch mergeAction { case merkletrie.Insert: - return nil, fmt.Errorf("%s merge should never be Insert while the other diff is Modify, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues %w", base.Path(), ErrActionNotMatch) + return false, fmt.Errorf("%s merge should never be Insert while the other diff is Modify, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues %w", base.Path(), ErrActionNotMatch) case merkletrie.Delete: - return cw.resolveConflict(base, merge) + return true, nil case merkletrie.Modify: if bytes.Equal(base.To().Hash(), merge.To().Hash()) { - return base, nil + return false, nil } - return cw.resolveConflict(base, merge) + return true, nil } } //should never come here - return nil, ErrActionNotMatch + return false, ErrActionNotMatch +} + +// ChangesMergeIter walk two changes set and merge changes +type ChangesMergeIter struct { + changePairIter *ChangesPairIter + resolver ConflictResolver +} + +// NewChangesMergeIter create a changes iter with two changes set and resolver function +func NewChangesMergeIter(baseChanges *Changes, mergerChanges *Changes, resolver ConflictResolver) *ChangesMergeIter { + return &ChangesMergeIter{changePairIter: NewChangesPairIter(baseChanges, mergerChanges), resolver: resolver} +} + +// Has check if any changes exit +func (cw *ChangesMergeIter) Has() bool { + return cw.changePairIter.Has() +} + +// Reset reset changes +func (cw *ChangesMergeIter) Reset() { + cw.changePairIter.Reset() +} + +// Next find change file, first sort each file in change, pop files from two changes, compare filename, +// +// base file < merge file, pop base change and put merge file back to queue +// base file > merge file, pop merge file and put base file back to queue +// both file name match, try to resolve file changes +// if one of the queue consume up, pick left change in the other queue +func (cw *ChangesMergeIter) Next() (IChange, error) { + chPair, err := cw.changePairIter.Next() + if err != nil { + return nil, err // when iter all, return io.EOF + } + + if chPair.IsConflict { + return cw.resolveConflict(chPair.Base, chPair.Merge) + } + if chPair.Base != nil { + return chPair.Base, nil + } + return chPair.Merge, nil } func (cw *ChangesMergeIter) resolveConflict(base, merge IChange) (IChange, error) { diff --git a/versionmgr/conflict.go b/versionmgr/conflict.go new file mode 100644 index 00000000..5c27ecb9 --- /dev/null +++ b/versionmgr/conflict.go @@ -0,0 +1,104 @@ +package versionmgr + +import ( + "bytes" + "fmt" + + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" +) + +// ConflictResolver resolve conflict between two change +type ConflictResolver func(base IChange, merged IChange) (IChange, error) + +// LeastHashResolve use least hash change for test +func LeastHashResolve(base IChange, merged IChange) (IChange, error) { + baseAction, err := base.Action() + if err != nil { + return nil, err + } + + mergeAction, err := merged.Action() + if err != nil { + return nil, err + } + + if baseAction == merkletrie.Delete { + return merged, nil + } + if mergeAction == merkletrie.Delete { + return base, nil + } + + if bytes.Compare(base.To().Hash(), merged.To().Hash()) < 0 { + return base, nil + } + return merged, nil +} + +func ForbidResolver(_ IChange, _ IChange) (IChange, error) { + return nil, fmt.Errorf("not allow conflict in this mode") +} + +type BaseChange struct { + Action merkletrie.Action + Hash hash.Hash +} + +type Conflict struct { + Path string + Base BaseChange + Merge BaseChange +} + +func OneSideResolver(isBase bool) ConflictResolver { + return func(base IChange, merge IChange) (IChange, error) { + if isBase { + return base, nil + } + return merge, nil + } +} + +func ResolveFromSelector(resolveMsg map[string]string) ConflictResolver { + return func(base IChange, merge IChange) (IChange, error) { + if resolveMsg[base.Path()] == "base" { + return base, nil + } + return merge, nil + } +} + +func ConflictCollector() ConflictResolver { + changes := make([]Conflict, 0) + return func(base IChange, merge IChange) (IChange, error) { + baseAct, err := base.Action() + if err != nil { + return nil, err + } + baseHash := hash.EmptyHash + if baseAct != merkletrie.Delete { + baseHash = base.To().Hash() + } + mergeAct, err := merge.Action() + if err != nil { + return nil, err + } + mergeHash := hash.EmptyHash + if mergeAct != merkletrie.Delete { + mergeHash = merge.To().Hash() + } + changes = append(changes, Conflict{ + Path: base.Path(), + Base: BaseChange{ + Action: baseAct, + Hash: baseHash, + }, + Merge: BaseChange{ + Action: mergeAct, + Hash: mergeHash, + }, + }) + return base, nil + } +} diff --git a/versionmgr/work_repo.go b/versionmgr/work_repo.go index 420c6149..7a986678 100644 --- a/versionmgr/work_repo.go +++ b/versionmgr/work_repo.go @@ -594,8 +594,7 @@ func (repository *WorkRepository) GetCommitChanges(ctx context.Context, pathPref return repository.DiffCommit(ctx, repository.commit.ParentHashes[1], pathPrefix) } -// Merge implement merge like git, docs https://en.wikipedia.org/wiki/Merge_(version_control) -func (repository *WorkRepository) Merge(ctx context.Context, merger *models.User, toMergeCommitHash hash.Hash, msg string, resolver ConflictResolver) (*models.Commit, error) { +func (repository *WorkRepository) GetMergeState(ctx context.Context, merger *models.User, toMergeCommitHash hash.Hash) ([]*ChangePair, error) { if repository.state != InBranch { return nil, errors.New("must merge on branch") } @@ -608,21 +607,95 @@ func (repository *WorkRepository) Merge(ctx context.Context, merger *models.User } } - newCommit, err := merge(ctx, - repository.repo.CommitRepo(repository.repoModel.ID), - repository.repo.FileTreeRepo(repository.repoModel.ID), - merger, - commit, - repository.repoModel, - toMergeCommitHash, - msg, - resolver) + var toMergeCommit *models.Commit + if !toMergeCommitHash.IsEmpty() { + toMergeCommit, err = repository.repo.CommitRepo(repository.repoModel.ID).Commit(ctx, toMergeCommitHash) + if err != nil { + return nil, err + } + } + + var bestAncestor *models.Commit + err = repository.repo.Transaction(ctx, func(repo models.IRepo) error { + commitRepo := repo.CommitRepo(repository.repoModel.ID) + fileTreeRepo := repo.FileTreeRepo(repository.repoModel.ID) + var err error + bestAncestor, err = findBestAncestor(ctx, commitRepo, fileTreeRepo, merger, repository.repoModel, commit, toMergeCommit) + if err != nil { + return err + } + + return ErrStop + }) + if err != nil && !errors.Is(err, ErrStop) { + return nil, err + } + + ancestorWorkTree, err := NewWorkTree(ctx, repository.repo.FileTreeRepo(repository.repoModel.ID), models.NewRootTreeEntry(bestAncestor.TreeHash)) if err != nil { return nil, err } - updateParams := models.NewUpdateBranchParams(repository.branch.ID).SetCommitHash(newCommit.Hash) - err = repository.repo.BranchRepo().UpdateByID(ctx, updateParams) + baseDiff, err := ancestorWorkTree.Diff(ctx, treeHashFromCommit(commit)) + if err != nil { + return nil, err + } + + mergeDiff, err := ancestorWorkTree.Diff(ctx, treeHashFromCommit(toMergeCommit)) + if err != nil { + return nil, err + } + + changePairs := make([]*ChangePair, 0) + iter := NewChangesPairIter(baseDiff, mergeDiff) + for iter.Has() { + changePair, err := iter.Next() + if err != nil { + return nil, err + } + changePairs = append(changePairs, changePair) + } + return changePairs, nil +} + +// Merge implement merge like git, docs https://en.wikipedia.org/wiki/Merge_(version_control) +func (repository *WorkRepository) Merge(ctx context.Context, merger *models.User, toMergeCommitHash hash.Hash, msg string, resolver ConflictResolver) (*models.Commit, error) { + if repository.state != InBranch { + return nil, errors.New("must merge on branch") + } + var commit *models.Commit + var err error + if !repository.branch.CommitHash.IsEmpty() { + commit, err = repository.repo.CommitRepo(repository.repoModel.ID).Commit(ctx, repository.branch.CommitHash) + if err != nil { + return nil, err + } + } + + var toMergeCommit *models.Commit + if !toMergeCommitHash.IsEmpty() { + toMergeCommit, err = repository.repo.CommitRepo(repository.repoModel.ID).Commit(ctx, toMergeCommitHash) + if err != nil { + return nil, err + } + } + + var newCommit *models.Commit + err = repository.repo.Transaction(ctx, func(repo models.IRepo) error { + commitRepo := repo.CommitRepo(repository.repoModel.ID) + fileTreeRepo := repo.FileTreeRepo(repository.repoModel.ID) + bestAncestor, err := findBestAncestor(ctx, commitRepo, fileTreeRepo, merger, repository.repoModel, commit, toMergeCommit) + if err != nil { + return err + } + newCommit, err = merge(ctx, commitRepo, fileTreeRepo, repository.repoModel, merger, bestAncestor, commit, toMergeCommit, msg, resolver) + if err != nil { + return err + } + + updateParams := models.NewUpdateBranchParams(repository.branch.ID).SetCommitHash(newCommit.Hash) + return repo.BranchRepo().UpdateByID(ctx, updateParams) + }) if err != nil { return nil, err } @@ -649,18 +722,24 @@ func (repository *WorkRepository) Reset() { repository.setCurState("", nil, nil, nil) } -func merge(ctx context.Context, +func findBestAncestor(ctx context.Context, commitRepo models.ICommitRepo, fileTreeRepo models.IFileTreeRepo, merger *models.User, - baseCommit *models.Commit, repoModel *models.Repository, - toMergeCommitHash hash.Hash, msg string, resolver ConflictResolver) (*models.Commit, error) { - toMergeCommit, err := commitRepo.Commit(ctx, toMergeCommitHash) - if err != nil { - return nil, err + baseCommit *models.Commit, + toMergeCommit *models.Commit) (*models.Commit, error) { + + if baseCommit == nil && toMergeCommit == nil { + return nil, errors.New("cannot find nil commit") } + if baseCommit == nil && toMergeCommit != nil { + return toMergeCommit, nil + } + if baseCommit != nil && toMergeCommit == nil { + return baseCommit, nil + } //find accessor baseCommitNode := NewWrapCommitNode(commitRepo, baseCommit) toMergeCommitNode := NewWrapCommitNode(commitRepo, toMergeCommit) @@ -673,7 +752,7 @@ func merge(ctx context.Context, } if mergeIsAncestorOfBase { - workRepoLog.Warnf("merge commit %s is ancestor of base commit %s", toMergeCommitHash, baseCommit.Hash) + workRepoLog.Warnf("merge commit %s is ancestor of base commit %s", toMergeCommit.Hash, baseCommit.Hash) return baseCommit, nil } } @@ -686,7 +765,7 @@ func merge(ctx context.Context, } if baseIsAncestorOfMerge { - workRepoLog.Warnf("base commit %s is ancestor of merge commit %s", toMergeCommitHash, baseCommit.Hash) + workRepoLog.Warnf("base commit %s is ancestor of merge commit %s", toMergeCommit.Hash, baseCommit.Hash) return toMergeCommit, nil } } @@ -704,15 +783,43 @@ func merge(ctx context.Context, bestCommit := bestAncestor[0] if len(bestAncestor) > 1 { //merge cross merge create virtual commit - virtualCommit, err := merge(ctx, commitRepo, fileTreeRepo, merger, bestAncestor[0].Commit(), repoModel, bestAncestor[1].Commit().Hash, "virtual commit", resolver) + subBestAncestor, err := findBestAncestor(ctx, commitRepo, fileTreeRepo, merger, repoModel, bestAncestor[0].Commit(), bestAncestor[1].Commit()) + virtualCommit, err := merge(ctx, commitRepo, fileTreeRepo, repoModel, merger, subBestAncestor, bestAncestor[0].Commit(), bestAncestor[1].Commit(), "virtual commit", ForbidResolver) if err != nil { return nil, err } bestCommit = NewWrapCommitNode(commitRepo, virtualCommit) } + return bestCommit.Commit(), nil +} + +// merge +// todo too much arguments, need a better solution +func merge(ctx context.Context, + commitRepo models.ICommitRepo, + fileTreeRepo models.IFileTreeRepo, + repoModel *models.Repository, + merger *models.User, + bestAncestor *models.Commit, + baseCommit *models.Commit, + toMergeCommit *models.Commit, + msg string, + resolver ConflictResolver) (*models.Commit, error) { + if baseCommit == nil && toMergeCommit == nil { + return nil, errors.New("cannot find nil commit") + } - ancestorWorkTree, err := NewWorkTree(ctx, fileTreeRepo, models.NewRootTreeEntry(bestAncestor[0].TreeHash())) + if baseCommit != nil && toMergeCommit == nil { + //do nothing + return baseCommit, nil + } + + if baseCommit == nil && toMergeCommit != nil { + return toMergeCommit, nil + } + + ancestorWorkTree, err := NewWorkTree(ctx, fileTreeRepo, models.NewRootTreeEntry(bestAncestor.TreeHash)) if err != nil { return nil, err } @@ -728,7 +835,7 @@ func merge(ctx context.Context, } //merge diff - baseWorkTree, err := NewWorkTree(ctx, fileTreeRepo, models.NewRootTreeEntry(bestCommit.Commit().TreeHash)) + baseWorkTree, err := NewWorkTree(ctx, fileTreeRepo, models.NewRootTreeEntry(bestAncestor.TreeHash)) if err != nil { return nil, err } @@ -759,7 +866,7 @@ func merge(ctx context.Context, MergeTag: "", Message: msg, TreeHash: baseWorkTree.Root().Hash(), - ParentHashes: []hash.Hash{baseCommit.Hash, toMergeCommitHash}, + ParentHashes: []hash.Hash{baseCommit.Hash, toMergeCommit.Hash}, CreatedAt: time.Now(), UpdatedAt: time.Now(), } @@ -769,9 +876,23 @@ func merge(ctx context.Context, } mergeCommit.Hash = hash - mergeCommitResult, err := commitRepo.Insert(ctx, mergeCommit) + mergeCommit, err = commitRepo.Insert(ctx, mergeCommit) if err != nil { return nil, err } - return mergeCommitResult, nil + return mergeCommit, nil +} + +func treeHashFromCommit(commit *models.Commit) hash.Hash { + if commit != nil { + return commit.TreeHash + } + return hash.EmptyHash +} + +func commitHashFromCommit(commit *models.Commit) hash.Hash { + if commit != nil { + return commit.Hash + } + return hash.EmptyHash } From d4d4b3c129f81c43143172475f79dad0674b31d2 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Fri, 5 Jan 2024 16:27:55 +0800 Subject: [PATCH 138/210] feat: impl mergerequest api --- api/swagger.yml | 155 +++++++- controller/commit_ctl.go | 2 +- controller/merge_request_ctl.go | 361 +++++++++++++++++- controller/object_ctl.go | 2 +- controller/wip_ctl.go | 81 +++- go.mod | 3 - go.sum | 9 - integrationtest/merge_request_test.go | 45 +++ integrationtest/root_test.go | 11 +- integrationtest/wip_object_test.go | 120 ++++++ models/branch.go | 63 +-- models/branch_test.go | 4 +- models/merge_request.go | 118 +++++- models/merge_request_test.go | 88 ++++- models/repository.go | 135 ++++--- models/tree.go | 8 +- models/user.go | 36 +- models/wip.go | 125 +++--- utils/hash/hash.go | 6 +- utils/hash/hash_test.go | 2 +- versionmgr/changes.go | 44 +-- versionmgr/commit_node_test.go | 16 +- versionmgr/commit_walker_bfs_filtered_test.go | 14 +- versionmgr/commit_walker_bfs_test.go | 14 +- versionmgr/commit_walker_ctime_test.go | 14 +- versionmgr/commit_walker_test.go | 28 +- versionmgr/conflict.go | 4 +- versionmgr/work_repo.go | 91 +++-- versionmgr/work_repo_diff_test.go | 2 +- versionmgr/work_repo_merge_test.go | 20 +- versionmgr/work_repo_test.go | 2 +- 31 files changed, 1275 insertions(+), 348 deletions(-) create mode 100644 integrationtest/merge_request_test.go diff --git a/api/swagger.yml b/api/swagger.yml index b4174bf7..e0dd5150 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -164,6 +164,19 @@ components: type: string description: type: string + MergeMergeRequest: + type: object + required: + - msg + - conflict_resolve + properties: + msg: + type: string + conflict_resolve: + description: use to record the resolution of the conflict, example({"b/a.txt":"base"}) + type: object + additionalProperties: + type: string MergeRequest: type: object required: @@ -209,6 +222,56 @@ components: updated_at: type: string format: date-time + MergeRequestFullState: + type: object + required: + - id + - target_branch + - source_branch + - source_repo_id + - target_repo_id + - title + - changes + - merge_status + - author_id + - created_at + - updated_at + properties: + id: + type: integer + format: uint64 + target_branch: + type: string + format: uuid + source_branch: + type: string + format: uuid + source_repo_id: + type: string + format: uuid + target_repo_id: + type: string + format: uuid + title: + type: string + merge_status: + type: integer + format: int + description: + type: string + author_id: + type: string + format: uuid + changes: + type: array + items: + $ref: "#/components/schemas/ChangePair" + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time MergeRequestList: type: object required: @@ -534,6 +597,13 @@ components: updated_at: type: string format: date-time + UpdateWip: + type: object + properties: + base_commit: + type: string + current_tree: + type: string Change: type: object required: @@ -549,6 +619,20 @@ components: type: string to_hash: type: string + ChangePair: + type: object + required: + - path + - is_conflict + properties: + path: + type: string + left: + $ref: "#/components/schemas/Change" + right: + $ref: "#/components/schemas/Change" + is_conflict: + type: boolean UserUpdate: type: object required: @@ -1099,6 +1183,26 @@ paths: description: Unauthorized 403: description: Forbidden + post: + tags: + - wip + operationId: updateWip + summary: update wip + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/UpdateWip" + responses: + 200: + description: update working in process success + 400: + description: ValidationError + 401: + description: Unauthorized + 403: + description: Forbidden delete: tags: - wip @@ -1592,7 +1696,53 @@ paths: 500: description: Internal Server Error - /mergequest/{owner}/{repository}/mr/{mrId}: + /mergequest/{owner}/{repository}/{mrId}/merge: + parameters: + - in: path + name: owner + required: true + schema: + type: string + - in: path + name: repository + required: true + schema: + type: string + - in: path + name: mrId + required: true + schema: + type: integer + format: uint64 + post: + tags: + - mergerequest + operationId: merge + summary: merge a mergerequest + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/MergeMergeRequest" + responses: + 200: + description: merge mergerequest success + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Commit" + 401: + description: Unauthorized + 404: + description: Resource Not Found + 420: + description: Too many requests + 500: + description: Internal Server Error + /mergequest/{owner}/{repository}/{mrId}: parameters: - in: path name: owner @@ -1621,7 +1771,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/MergeRequest" + $ref: "#/components/schemas/MergeRequestFullState" 401: description: Unauthorized 404: @@ -1669,7 +1819,6 @@ paths: 500: description: Internal Server Error - /users/{owner}/repos: parameters: - in: path diff --git a/controller/commit_ctl.go b/controller/commit_ctl.go index e992af5c..42b71bfb 100644 --- a/controller/commit_ctl.go +++ b/controller/commit_ctl.go @@ -48,7 +48,7 @@ func (commitCtl CommitController) GetEntriesInRef(ctx context.Context, w *api.Ji return } - treeHash := hash.EmptyHash + treeHash := hash.Empty if params.Type == api.RefTypeWip { refName := repository.HEAD if params.Ref != nil { diff --git a/controller/merge_request_ctl.go b/controller/merge_request_ctl.go index 0b7f8812..42c5a603 100644 --- a/controller/merge_request_ctl.go +++ b/controller/merge_request_ctl.go @@ -2,9 +2,16 @@ package controller import ( "context" + "fmt" "net/http" "time" + "github.com/jiaozifs/jiaozifs/utils/hash" + + "github.com/jiaozifs/jiaozifs/utils" + + "github.com/jiaozifs/jiaozifs/versionmgr" + "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/auth" "github.com/jiaozifs/jiaozifs/block/params" @@ -19,9 +26,75 @@ type MergeRequestController struct { PublicStorageConfig params.AdapterConfig } -func (mrCtl MergeRequestController) ListMergeRequests(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, ownerName string, repositoryName string, params api.ListMergeRequestsParams) { - //TODO implement me - panic("implement me") +func (mrCtl MergeRequestController) ListMergeRequests(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.ListMergeRequestsParams) { + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return + } + + owner, err := mrCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + repository, err := mrCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) + if err != nil { + w.Error(err) + return + } + + if operator.Name != owner.Name { + w.Forbidden() + return + } + + listParams := models.NewListMergeRequestParams().SetTargetRepoID(repository.ID) + + if params.After != nil { + listParams.SetAfter(*params.After) + } + pageAmount := utils.IntValue(params.Amount) + if pageAmount > utils.DefaultMaxPerPage || pageAmount <= 0 { + listParams.SetAmount(utils.DefaultMaxPerPage) + } else { + listParams.SetAmount(pageAmount) + } + + mrs, hasMore, err := mrCtl.Repo.MergeRequestRepo().List(ctx, listParams) + if err != nil { + w.Error(err) + return + } + + results := make([]api.MergeRequest, len(mrs)) + for index, mr := range mrs { + results[index] = api.MergeRequest{ + Id: mr.ID, + Title: mr.Title, + Description: mr.Description, + AuthorId: mr.AuthorID, + MergeStatus: int(mr.MergeStatus), + SourceBranch: mr.SourceBranch, + SourceRepoId: mr.SourceRepoID, + TargetBranch: mr.TargetBranch, + TargetRepoId: mr.TargetRepoID, + CreatedAt: mr.CreatedAt, + UpdatedAt: mr.UpdatedAt, + } + } + pagMag := utils.PaginationFor(hasMore, results, "UpdatedAt") + pagination := api.Pagination{ + HasMore: pagMag.HasMore, + MaxPerPage: pagMag.MaxPerPage, + NextOffset: pagMag.NextOffset, + Results: pagMag.Results, + } + w.JSON(api.MergeRequestList{ + Pagination: pagination, + Results: results, + }) } func (mrCtl MergeRequestController) CreateMergeRequest(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.CreateMergeRequestJSONRequestBody, ownerName string, repositoryName string) { @@ -49,6 +122,10 @@ func (mrCtl MergeRequestController) CreateMergeRequest(ctx context.Context, w *a return } + if body.SourceBranchName == body.TargetBranchName { + w.BadRequest(fmt.Sprintf("source branch name %s and target branch name %s can not be same", body.SourceBranchName, body.TargetBranchName)) + } + sorceBranch, err := mrCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(body.SourceBranchName)) if err != nil { w.Error(err) @@ -74,6 +151,44 @@ func (mrCtl MergeRequestController) CreateMergeRequest(ctx context.Context, w *a UpdatedAt: time.Now(), }) + if err != nil { + w.Error(err) + return + } + + workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repository, mrCtl.Repo, mrCtl.PublicStorageConfig) + if err != nil { + w.Error(err) + return + } + + err = workRepo.CheckOut(ctx, versionmgr.InBranch, sorceBranch.Name) + if err != nil { + w.Error(err) + return + } + + changePairs, err := workRepo.GetMergeState(ctx, targetBranch.CommitHash) + if err != nil { + w.Error(err) + return + } + + resp := api.MergeRequestFullState{ + Id: mrModel.ID, + Title: mrModel.Title, + Description: mrModel.Description, + AuthorId: mrModel.AuthorID, + MergeStatus: int(mrModel.MergeStatus), + SourceBranch: mrModel.SourceBranch, + SourceRepoId: mrModel.SourceRepoID, + TargetBranch: mrModel.TargetBranch, + TargetRepoId: mrModel.TargetRepoID, + CreatedAt: mrModel.CreatedAt, + UpdatedAt: mrModel.UpdatedAt, + } + + resp.Changes, err = changePairToDTO(changePairs) if err != nil { w.Error(err) return @@ -82,12 +197,153 @@ func (mrCtl MergeRequestController) CreateMergeRequest(ctx context.Context, w *a w.JSON(mrModel, http.StatusCreated) } -func (mrCtl MergeRequestController) DeleteMergeRequest(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, ownerName string, repositoryName string, mrID uint64) { - //TODO implement me - panic("implement me") +func (mrCtl MergeRequestController) DeleteMergeRequest(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, _ string, mrID uint64) { + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return + } + + owner, err := mrCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + if operator.Name != owner.Name { + w.Forbidden() + return + } + + affectRows, err := mrCtl.Repo.MergeRequestRepo().Delete(ctx, models.NewDeleteMergeRequestParams().SetID(mrID)) + if err != nil { + w.Error(err) + return + } + if affectRows == 0 { + w.NotFound() + return + } + w.OK() +} + +func (mrCtl MergeRequestController) GetMergeRequest(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, mrID uint64) { + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return + } + + owner, err := mrCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + if operator.Name != owner.Name { + w.Forbidden() + return + } + + // Get repo + repository, err := mrCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) + if err != nil { + w.Error(err) + return + } + + workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repository, mrCtl.Repo, mrCtl.PublicStorageConfig) + if err != nil { + w.Error(err) + return + } + + mergeRequest, err := mrCtl.Repo.MergeRequestRepo().Get(ctx, models.NewGetMergeRequestParams().SetID(mrID)) + if err != nil { + w.Error(err) + return + } + + sourceBranch, err := mrCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetID(mergeRequest.SourceRepoID)) + if err != nil { + w.Error(err) + return + } + + targetBranch, err := mrCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetID(mergeRequest.TargetBranch)) + if err != nil { + w.Error(err) + return + } + + err = workRepo.CheckOut(ctx, versionmgr.InBranch, sourceBranch.Name) + if err != nil { + w.Error(err) + return + } + + changePairs, err := workRepo.GetMergeState(ctx, targetBranch.CommitHash) + if err != nil { + w.Error(err) + return + } + + resp := api.MergeRequestFullState{ + Id: mergeRequest.ID, + Title: mergeRequest.Title, + Description: mergeRequest.Description, + AuthorId: mergeRequest.AuthorID, + MergeStatus: int(mergeRequest.MergeStatus), + SourceBranch: mergeRequest.SourceBranch, + SourceRepoId: mergeRequest.SourceRepoID, + TargetBranch: mergeRequest.TargetBranch, + TargetRepoId: mergeRequest.TargetRepoID, + CreatedAt: mergeRequest.CreatedAt, + UpdatedAt: mergeRequest.UpdatedAt, + } + resp.Changes, err = changePairToDTO(changePairs) + if err != nil { + w.Error(err) + return + } + w.JSON(resp) +} + +func (mrCtl MergeRequestController) UpdateMergeRequest(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.UpdateMergeRequestJSONRequestBody, ownerName string, repositoryName string, mrID uint64) { + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return + } + + owner, err := mrCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + if operator.Name != owner.Name { + w.Forbidden() + return + } + + updateParams := models.NewUpdateMergeRequestParams(mrID) + if body.Title != nil { + updateParams.SetTitle(utils.StringValue(body.Title)) + } + if body.Description != nil { + updateParams.SetDescription(utils.StringValue(body.Description)) + } + + err = mrCtl.Repo.MergeRequestRepo().UpdateByID(ctx, updateParams) + if err != nil { + w.Error(err) + return + } + w.OK() } -func (mrCtl MergeRequestController) GetMergeRequest(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, ownerName string, repositoryName string, mrID uint64) { +func (mrCtl MergeRequestController) Merge(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.MergeJSONRequestBody, ownerName string, repositoryName string, mrID uint64) { operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) @@ -111,9 +367,94 @@ func (mrCtl MergeRequestController) GetMergeRequest(ctx context.Context, w *api. w.Error(err) return } + + mergeRequest, err := mrCtl.Repo.MergeRequestRepo().Get(ctx, models.NewGetMergeRequestParams().SetID(mrID)) + if err != nil { + w.Error(err) + return + } + + workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repository, mrCtl.Repo, mrCtl.PublicStorageConfig) + if err != nil { + w.Error(err) + return + } + + sourceBranch, err := mrCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetID(mergeRequest.SourceRepoID)) + if err != nil { + w.Error(err) + return + } + + targetBranch, err := mrCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetID(mergeRequest.TargetBranch)) + if err != nil { + w.Error(err) + return + } + + err = workRepo.CheckOut(ctx, versionmgr.InBranch, sourceBranch.Name) + if err != nil { + w.Error(err) + return + } + + merge, err := workRepo.Merge(ctx, targetBranch.CommitHash, body.Msg, versionmgr.ResolveFromSelector(body.ConflictResolve)) + if err != nil { + w.Error(err) + return + } + w.JSON(merge) } -func (mrCtl MergeRequestController) UpdateMergeRequest(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, body api.UpdateMergeRequestJSONRequestBody, ownerName string, repositoryName string, mrID uint64) { - //TODO implement me - panic("implement me") +func changePairToDTO(pairs []*versionmgr.ChangePair) ([]api.ChangePair, error) { + + var changes = make([]api.ChangePair, len(pairs)) + for index, ch := range pairs { + var path string + if ch.Left != nil { + path = ch.Left.Path() + } else { + path = ch.Right.Path() + } + pair := api.ChangePair{ + Path: path, + IsConflict: ch.IsConflict, + } + + if ch.Left != nil { + leftAction, err := ch.Left.Action() + if err != nil { + return nil, err + } + pair.Left = &api.Change{ + Action: api.ChangeAction(leftAction), + Path: path, + } + if ch.Left.From() != nil { + pair.Left.BaseHash = utils.String(hash.Hash(ch.Left.From().Hash()).Hex()) + } + if ch.Left.To() != nil { + pair.Left.ToHash = utils.String(hash.Hash(ch.Left.To().Hash()).Hex()) + } + } + + if ch.Right != nil { + rightAction, err := ch.Right.Action() + if err != nil { + return nil, err + } + pair.Right = &api.Change{ + Action: api.ChangeAction(rightAction), + Path: path, + } + if ch.Right.From() != nil { + pair.Left.BaseHash = utils.String(hash.Hash(ch.Right.From().Hash()).Hex()) + } + if ch.Right.To() != nil { + pair.Left.ToHash = utils.String(hash.Hash(ch.Right.To().Hash()).Hex()) + } + } + changes[index] = pair + } + return changes, nil } diff --git a/controller/object_ctl.go b/controller/object_ctl.go index c229ec08..76056bc5 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -69,7 +69,7 @@ func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsRes return } - treeHash := hash.EmptyHash + treeHash := hash.Empty if !wip.CurrentTree.IsEmpty() { treeHash = wip.CurrentTree } diff --git a/controller/wip_ctl.go b/controller/wip_ctl.go index b3914159..ec1728cc 100644 --- a/controller/wip_ctl.go +++ b/controller/wip_ctl.go @@ -3,6 +3,7 @@ package controller import ( "bytes" "context" + "fmt" "net/http" "github.com/jiaozifs/jiaozifs/api" @@ -150,6 +151,84 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon w.JSON(workRepo.CurWip(), http.StatusCreated) } +func (wipCtl WipController) UpdateWip(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, body api.UpdateWipJSONRequestBody, ownerName string, repositoryName string, params api.UpdateWipParams) { + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return + } + + owner, err := wipCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + repository, err := wipCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) + if err != nil { + w.Error(err) + return + } + + if operator.Name != owner.Name { //todo check permission to operator ownerRepo + w.Forbidden() + return + } + + ref, err := wipCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(params.RefName)) + if err != nil { + w.Error(err) + return + } + + wip, err := wipCtl.Repo.WipRepo().Get(ctx, models.NewGetWipParams().SetCreatorID(operator.ID).SetRepositoryID(repository.ID).SetRefID(ref.ID)) + if err != nil { + w.Error(err) + return + } + + updateParams := models.NewUpdateWipParams(wip.ID) + if body.BaseCommit != nil { + baseCommitHash, err := hash.FromHex(*body.BaseCommit) + if err != nil { + w.Error(err) + return + } + + if !baseCommitHash.IsEmpty() { + _, err = wipCtl.Repo.CommitRepo(repository.ID).Commit(ctx, baseCommitHash) + if err != nil { + w.Error(fmt.Errorf("unable to get commit hash %s: %w", baseCommitHash, err)) + return + } + } + updateParams.SetBaseCommit(baseCommitHash) + } + if body.CurrentTree != nil { + currentTreeHash, err := hash.FromHex(*body.CurrentTree) + if err != nil { + w.Error(err) + return + } + + if !currentTreeHash.IsEmpty() { + _, err = wipCtl.Repo.FileTreeRepo(repository.ID).TreeNode(ctx, currentTreeHash) + if err != nil { + w.Error(fmt.Errorf("unable to get tree root %s: %w", currentTreeHash, err)) + return + } + } + updateParams.SetCurrentTree(currentTreeHash) + } + + err = wipCtl.Repo.WipRepo().UpdateByID(ctx, updateParams) + if err != nil { + w.Error(err) + return + } + w.OK() +} + // DeleteWip delete active working in process operator only can delete himself wip func (wipCtl WipController) DeleteWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.DeleteWipParams) { operator, err := auth.GetOperator(ctx) @@ -232,7 +311,7 @@ func (wipCtl WipController) GetWipChanges(ctx context.Context, w *api.JiaozifsRe return } - treeHash := hash.EmptyHash + treeHash := hash.Empty if !wip.BaseCommit.IsEmpty() { commit, err := wipCtl.Repo.CommitRepo(repository.ID).Commit(ctx, wip.BaseCommit) if err != nil { diff --git a/go.mod b/go.mod index d1c3daf1..95b6090b 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,6 @@ require ( github.com/flowchartsman/swaggerui v0.0.0-20221017034628-909ed4f3701b github.com/getkin/kin-openapi v0.118.0 github.com/go-chi/chi/v5 v5.0.10 - github.com/go-git/go-git/v5 v5.10.1 github.com/go-openapi/swag v0.22.4 github.com/go-test/deep v1.1.0 github.com/golang-jwt/jwt v3.2.2+incompatible @@ -37,7 +36,6 @@ require ( github.com/minio/minio-go/v7 v7.0.64 github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/mapstructure v1.5.0 - github.com/oapi-codegen/nethttp-middleware v1.0.1 github.com/oapi-codegen/runtime v1.1.0 github.com/ory/dockertest/v3 v3.10.0 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 @@ -146,7 +144,6 @@ require ( github.com/opencontainers/runc v1.1.7 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/perimeterx/marshmallow v1.1.4 // indirect - github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect diff --git a/go.sum b/go.sum index 1822d1a8..0b539cee 100644 --- a/go.sum +++ b/go.sum @@ -136,7 +136,6 @@ github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1A github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -175,10 +174,6 @@ github.com/getkin/kin-openapi v0.118.0 h1:z43njxPmJ7TaPpMSCQb7PN0dEYno4tyBPQcrFd github.com/getkin/kin-openapi v0.118.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= -github.com/go-git/go-git/v5 v5.10.1 h1:tu8/D8i+TWxgKpzQ3Vc43e+kkhXqtsZCKI/egajKnxk= -github.com/go-git/go-git/v5 v5.10.1/go.mod h1:uEuHjxkHap8kAl//V5F/nNWwqIYtP/402ddd05mp0wg= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -376,8 +371,6 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= -github.com/oapi-codegen/nethttp-middleware v1.0.1 h1:ZWvwfnMU0eloHX1VEJmQscQm3741t0vCm0eSIie1NIo= -github.com/oapi-codegen/nethttp-middleware v1.0.1/go.mod h1:P7xtAvpoqNB+5obR9qRCeefH7YlXWSK3KgPs/9WB8tE= github.com/oapi-codegen/runtime v1.1.0 h1:rJpoNUawn5XTvekgfkvSZr0RqEnoYpFkyvrzfWeFKWM= github.com/oapi-codegen/runtime v1.1.0/go.mod h1:BeSfBkWWWnAnGdyS+S/GnlbmHKzf8/hwkvelJZDeKA8= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -394,8 +387,6 @@ github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55F github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= -github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= -github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= diff --git a/integrationtest/merge_request_test.go b/integrationtest/merge_request_test.go new file mode 100644 index 00000000..8169a8f9 --- /dev/null +++ b/integrationtest/merge_request_test.go @@ -0,0 +1,45 @@ +package integrationtest + +import ( + "context" + "net/http" + + "github.com/jiaozifs/jiaozifs/utils" + + "github.com/jiaozifs/jiaozifs/api" + apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" + "github.com/smartystreets/goconvey/convey" +) + +func MergeRequestSpec(ctx context.Context, urlStr string) func(c convey.C) { + client, _ := api.NewClient(urlStr + apiimpl.APIV1Prefix) + return func(c convey.C) { + userName := "navi" + repoName := "mr_test" + branchName := "feat/obj_test" + + createUser(ctx, c, client, userName) + loginAndSwitch(ctx, c, client, "molly login", userName, false) + createRepo(ctx, c, client, repoName) + createBranch(ctx, c, client, userName, repoName, "main", branchName) + createWip(ctx, c, client, "feat get obj test", userName, repoName, branchName) + uploadObject(ctx, c, client, "update f1 to test branch", userName, repoName, branchName, "a.bin") + commitWip(ctx, c, client, "commit delete object", userName, repoName, branchName, "test") + + c.Convey("create merge request", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.CreateMergeRequest(ctx, userName, repoName, api.CreateMergeRequestJSONRequestBody{ + Description: utils.String("create merge request test"), + SourceBranchName: branchName, + TargetBranchName: branchName, + Title: "Merge: test", + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + }) + } +} diff --git a/integrationtest/root_test.go b/integrationtest/root_test.go index d9065f27..407a3c6d 100644 --- a/integrationtest/root_test.go +++ b/integrationtest/root_test.go @@ -15,10 +15,15 @@ func TestSpec(t *testing.T) { convey.Convey("user test", t, UserSpec(ctx, urlStr)) convey.Convey("repo test", t, RepoSpec(ctx, urlStr)) convey.Convey("branch test", t, BranchSpec(ctx, urlStr)) - convey.Convey("wip test", t, WipSpec(ctx, urlStr)) + convey.Convey("object test", t, ObjectSpec(ctx, urlStr)) + + convey.Convey("wip test", t, WipSpec(ctx, urlStr)) convey.Convey("wip object test", t, WipObjectSpec(ctx, urlStr)) - convey.Convey("commit test", t, GetEntriesInRefSpec(ctx, urlStr)) - convey.Convey("commit test", t, GetCommitChangesSpec(ctx, urlStr)) + convey.Convey("update wip test", t, UpdateWipSpec(ctx, urlStr)) + + convey.Convey("get entries test", t, GetEntriesInRefSpec(ctx, urlStr)) + convey.Convey("commit changes test", t, GetCommitChangesSpec(ctx, urlStr)) + convey.Convey("mergee request test", t, MergeRequestSpec(ctx, urlStr)) } diff --git a/integrationtest/wip_object_test.go b/integrationtest/wip_object_test.go index d043283a..cc1fdc02 100644 --- a/integrationtest/wip_object_test.go +++ b/integrationtest/wip_object_test.go @@ -4,6 +4,8 @@ import ( "context" "net/http" + "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/jiaozifs/jiaozifs/api" apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" "github.com/jiaozifs/jiaozifs/utils" @@ -457,3 +459,121 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { } } + +func UpdateWipSpec(ctx context.Context, urlStr string) func(c convey.C) { + client, _ := api.NewClient(urlStr + apiimpl.APIV1Prefix) + var wip *api.Wip + return func(c convey.C) { + userName := "milly" + repoName := "update_wip_test" + branchName := "main" + createUser(ctx, c, client, userName) + loginAndSwitch(ctx, c, client, "jude login", userName, false) + createRepo(ctx, c, client, repoName) + createWip(ctx, c, client, "get wip obj test", userName, repoName, branchName) + uploadObject(ctx, c, client, "update f1 to test branch", userName, repoName, branchName, "m.dat") + uploadObject(ctx, c, client, "update f2 to test branch", userName, repoName, branchName, "g/m.dat") + c.Convey("get wip", func(c convey.C) { + resp, err := client.GetWip(ctx, userName, repoName, &api.GetWipParams{ + RefName: branchName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseGetWipResponse(resp) + convey.So(err, convey.ShouldBeNil) + wip = result.JSON200 + }) + + c.Convey("update wip", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.UpdateWip(ctx, userName, repoName, &api.UpdateWipParams{RefName: branchName}, api.UpdateWipJSONRequestBody{ + CurrentTree: utils.String(hash.Empty.Hex()), + BaseCommit: utils.String(hash.Empty.Hex()), + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("update wip in not exit repo", func() { + resp, err := client.UpdateWip(ctx, userName, "mock_repo", &api.UpdateWipParams{RefName: branchName}, api.UpdateWipJSONRequestBody{ + CurrentTree: utils.String(hash.Empty.Hex()), + BaseCommit: utils.String(hash.Empty.Hex()), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("update wip in non exit user", func() { + resp, err := client.UpdateWip(ctx, "telo", repoName, &api.UpdateWipParams{RefName: branchName}, api.UpdateWipJSONRequestBody{ + CurrentTree: utils.String(hash.Empty.Hex()), + BaseCommit: utils.String(hash.Empty.Hex()), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("update wip in other's repo", func() { + resp, err := client.UpdateWip(ctx, "jimmy", "happygo", &api.UpdateWipParams{RefName: "main"}, api.UpdateWipJSONRequestBody{ + CurrentTree: utils.String(hash.Empty.Hex()), + BaseCommit: utils.String(hash.Empty.Hex()), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + + c.Convey("update wip in non exit branch", func() { + //delete + resp, err := client.UpdateWip(ctx, userName, repoName, &api.UpdateWipParams{RefName: "feat/mock_ref"}, api.UpdateWipJSONRequestBody{ + CurrentTree: utils.String(hash.Empty.Hex()), + BaseCommit: utils.String(hash.Empty.Hex()), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("update wip successful", func() { + //delete + resp, err := client.UpdateWip(ctx, userName, repoName, &api.UpdateWipParams{RefName: branchName}, api.UpdateWipJSONRequestBody{ + CurrentTree: utils.String(hash.Empty.Hex()), + BaseCommit: utils.String(hash.Empty.Hex()), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + //ensure delete work + getResp, err := client.GetWip(ctx, userName, repoName, &api.GetWipParams{RefName: branchName}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + updatedWip, err := api.ParseGetWipResponse(getResp) + convey.So(err, convey.ShouldBeNil) + convey.So((*updatedWip.JSON200).BaseCommit, convey.ShouldEqual, "") + convey.So((*updatedWip.JSON200).CurrentTree, convey.ShouldEqual, "") + }) + + c.Convey("update wip to non empty successful", func() { + //delete + resp, err := client.UpdateWip(ctx, userName, repoName, &api.UpdateWipParams{RefName: branchName}, api.UpdateWipJSONRequestBody{ + CurrentTree: utils.String(wip.CurrentTree), + BaseCommit: utils.String(wip.BaseCommit), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + //ensure delete work + getResp, err := client.GetWip(ctx, userName, repoName, &api.GetWipParams{RefName: branchName}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + updatedWip, err := api.ParseGetWipResponse(getResp) + convey.So(err, convey.ShouldBeNil) + convey.So((*updatedWip.JSON200).BaseCommit, convey.ShouldEqual, wip.BaseCommit) + convey.So((*updatedWip.JSON200).CurrentTree, convey.ShouldEqual, wip.CurrentTree) + }) + }) + } +} diff --git a/models/branch.go b/models/branch.go index 971c410f..3032037a 100644 --- a/models/branch.go +++ b/models/branch.go @@ -27,9 +27,9 @@ type Branch struct { } type GetBranchParams struct { - ID uuid.UUID - RepositoryID uuid.UUID - Name *string + id uuid.UUID + repositoryID uuid.UUID + name *string } func NewGetBranchParams() *GetBranchParams { @@ -37,24 +37,24 @@ func NewGetBranchParams() *GetBranchParams { } func (gup *GetBranchParams) SetID(id uuid.UUID) *GetBranchParams { - gup.ID = id + gup.id = id return gup } func (gup *GetBranchParams) SetRepositoryID(repositoryID uuid.UUID) *GetBranchParams { - gup.RepositoryID = repositoryID + gup.repositoryID = repositoryID return gup } func (gup *GetBranchParams) SetName(name string) *GetBranchParams { - gup.Name = &name + gup.name = &name return gup } type DeleteBranchParams struct { - ID uuid.UUID - RepositoryID uuid.UUID - Name *string + id uuid.UUID + repositoryID uuid.UUID + name *string } func NewDeleteBranchParams() *DeleteBranchParams { @@ -62,31 +62,30 @@ func NewDeleteBranchParams() *DeleteBranchParams { } func (gup *DeleteBranchParams) SetRepositoryID(repositoryID uuid.UUID) *DeleteBranchParams { - gup.RepositoryID = repositoryID + gup.repositoryID = repositoryID return gup } func (gup *DeleteBranchParams) SetID(id uuid.UUID) *DeleteBranchParams { - gup.ID = id + gup.id = id return gup } func (gup *DeleteBranchParams) SetName(name string) *DeleteBranchParams { - gup.Name = &name + gup.name = &name return gup } type UpdateBranchParams struct { - bun.BaseModel `bun:"table:branches"` - ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` - CommitHash hash.Hash `bun:"commit_hash,type:bytea,notnull"` + id uuid.UUID + commitHash hash.Hash } func NewUpdateBranchParams(id uuid.UUID) *UpdateBranchParams { - return &UpdateBranchParams{ID: id} + return &UpdateBranchParams{id: id} } func (up *UpdateBranchParams) SetCommitHash(commitHash hash.Hash) *UpdateBranchParams { - up.CommitHash = commitHash + up.commitHash = commitHash return up } @@ -154,16 +153,16 @@ func (r BranchRepo) Get(ctx context.Context, params *GetBranchParams) (*Branch, repo := &Branch{} query := r.db.NewSelect().Model(repo) - if uuid.Nil != params.ID { - query = query.Where("id = ?", params.ID) + if uuid.Nil != params.id { + query = query.Where("id = ?", params.id) } - if uuid.Nil != params.RepositoryID { - query = query.Where("repository_id = ?", params.RepositoryID) + if uuid.Nil != params.repositoryID { + query = query.Where("repository_id = ?", params.repositoryID) } - if params.Name != nil { - query = query.Where("name = ?", *params.Name) + if params.name != nil { + query = query.Where("name = ?", *params.name) } err := query.Limit(1).Scan(ctx) @@ -206,16 +205,16 @@ func (r BranchRepo) List(ctx context.Context, params *ListBranchParams) ([]*Bran func (r BranchRepo) Delete(ctx context.Context, params *DeleteBranchParams) (int64, error) { query := r.db.NewDelete().Model((*Branch)(nil)) - if uuid.Nil != params.ID { - query = query.Where("id = ?", params.ID) + if uuid.Nil != params.id { + query = query.Where("id = ?", params.id) } - if uuid.Nil != params.RepositoryID { - query = query.Where("repository_id = ?", params.RepositoryID) + if uuid.Nil != params.repositoryID { + query = query.Where("repository_id = ?", params.repositoryID) } - if params.Name != nil { - query = query.Where("name = ?", *params.Name) + if params.name != nil { + query = query.Where("name = ?", *params.name) } sqlResult, err := query.Exec(ctx) @@ -230,6 +229,10 @@ func (r BranchRepo) Delete(ctx context.Context, params *DeleteBranchParams) (int } func (r BranchRepo) UpdateByID(ctx context.Context, updateModel *UpdateBranchParams) error { - _, err := r.db.NewUpdate().Model(updateModel).WherePK().Exec(ctx) + updateQuery := r.db.NewUpdate().Model((*Branch)(nil)).Where("id = ?", updateModel.id) + if updateModel.commitHash != nil { + updateQuery.Set("commit_hash = ?", updateModel.commitHash) + } + _, err := updateQuery.Exec(ctx) return err } diff --git a/models/branch_test.go b/models/branch_test.go index df706c3b..2c938ca2 100644 --- a/models/branch_test.go +++ b/models/branch_test.go @@ -40,9 +40,7 @@ func TestRefRepoInsert(t *testing.T) { err = repo.UpdateByID(ctx, models.NewUpdateBranchParams(newBranch.ID).SetCommitHash(mockHash)) require.NoError(t, err) - branchAfterUpdated, err := repo.Get(ctx, &models.GetBranchParams{ - ID: newBranch.ID, - }) + branchAfterUpdated, err := repo.Get(ctx, models.NewGetBranchParams().SetID(newBranch.ID)) require.NoError(t, err) require.Equal(t, mockHash, branchAfterUpdated.CommitHash) diff --git a/models/merge_request.go b/models/merge_request.go index 9dbc5f96..5a28c821 100644 --- a/models/merge_request.go +++ b/models/merge_request.go @@ -17,10 +17,10 @@ const ( type MergeRequest struct { bun.BaseModel `bun:"table:merge_requests"` ID uint64 `bun:"id,pk,autoincrement" json:"id"` - TargetBranch uuid.UUID `bun:"target_branch,type:bytea,notnull" json:"target_branch"` - SourceBranch uuid.UUID `bun:"source_branch,type:bytea,notnull" json:"source_branch"` - SourceRepoID uuid.UUID `bun:"source_repo_id,type:bytea,notnull" json:"source_repo_id"` - TargetRepoID uuid.UUID `bun:"target_repo_id,type:bytea,notnull" json:"target_repo_id"` + TargetBranch uuid.UUID `bun:"target_branch,unique:ts_repo_ts_branch,type:bytea,notnull" json:"target_branch"` + SourceBranch uuid.UUID `bun:"source_branch,unique:ts_repo_ts_branch,type:bytea,notnull" json:"source_branch"` + SourceRepoID uuid.UUID `bun:"source_repo_id,unique:ts_repo_ts_branch,type:bytea,notnull" json:"source_repo_id"` + TargetRepoID uuid.UUID `bun:"target_repo_id,unique:ts_repo_ts_branch,type:bytea,notnull" json:"target_repo_id"` Title string `bun:"title,notnull" json:"title"` MergeStatus MergeStatus `bun:"merge_status,notnull" json:"merge_status"` Description *string `bun:"description" json:"description"` @@ -44,9 +44,74 @@ func (gmr *GetMergeRequestParams) SetID(id uint64) *GetMergeRequestParams { return gmr } +type DeleteMergeRequestParams struct { + id *uint64 +} + +func NewDeleteMergeRequestParams() *DeleteMergeRequestParams { + return &DeleteMergeRequestParams{} +} + +func (gmr *DeleteMergeRequestParams) SetID(id uint64) *DeleteMergeRequestParams { + gmr.id = &id + return gmr +} + +type UpdateMergeRequestParams struct { + id *uint64 + updateTime time.Time + title *string + description *string +} + +func NewUpdateMergeRequestParams(id uint64) *UpdateMergeRequestParams { + return &UpdateMergeRequestParams{ + id: &id, + updateTime: time.Now(), + } +} + +func (u *UpdateMergeRequestParams) SetTitle(title string) *UpdateMergeRequestParams { + u.title = &title + return u +} + +func (u *UpdateMergeRequestParams) SetDescription(description string) *UpdateMergeRequestParams { + u.description = &description + return u +} + +type ListMergeRequestParams struct { + after *time.Time + amount int + targetRepoID uuid.UUID +} + +func NewListMergeRequestParams() *ListMergeRequestParams { + return &ListMergeRequestParams{} +} + +func (lmr *ListMergeRequestParams) SetTargetRepoID(targetRepoID uuid.UUID) *ListMergeRequestParams { + lmr.targetRepoID = targetRepoID + return lmr +} + +func (lmr *ListMergeRequestParams) SetAfter(after time.Time) *ListMergeRequestParams { + lmr.after = &after + return lmr +} + +func (lmr *ListMergeRequestParams) SetAmount(amount int) *ListMergeRequestParams { + lmr.amount = amount + return lmr +} + type IMergeRequestRepo interface { Insert(ctx context.Context, ref *MergeRequest) (*MergeRequest, error) Get(ctx context.Context, params *GetMergeRequestParams) (*MergeRequest, error) + List(ctx context.Context, params *ListMergeRequestParams) ([]MergeRequest, bool, error) + UpdateByID(ctx context.Context, params *UpdateMergeRequestParams) error + Delete(ctx context.Context, params *DeleteMergeRequestParams) (int64, error) } var _ IMergeRequestRepo = (*MergeRequestRepo)(nil) @@ -77,3 +142,48 @@ func (m MergeRequestRepo) Get(ctx context.Context, params *GetMergeRequestParams return mergeRequest, query.Limit(1).Scan(ctx) } + +func (m MergeRequestRepo) List(ctx context.Context, params *ListMergeRequestParams) ([]MergeRequest, bool, error) { + mergeRequest := make([]MergeRequest, 0) + query := m.db.NewSelect().Model(&mergeRequest) + if params.targetRepoID != uuid.Nil { + query = query.Where("target_repo_id = ?", params.targetRepoID) + } + + query = query.Order("updated_at DESC") + if params.after != nil { + query = query.Where("updated_at > ?", *params.after) + } + + err := query.Limit(params.amount).Scan(ctx) + return mergeRequest, len(mergeRequest) == params.amount, err +} + +func (m MergeRequestRepo) Delete(ctx context.Context, params *DeleteMergeRequestParams) (int64, error) { + query := m.db.NewDelete().Model((*MergeRequest)(nil)) + if params.id != nil { + query = query.Where("id = ?", params.id) + } + + sqlResult, err := query.Exec(ctx) + if err != nil { + return 0, err + } + affectedRows, err := sqlResult.RowsAffected() + if err != nil { + return 0, err + } + return affectedRows, err +} + +func (m MergeRequestRepo) UpdateByID(ctx context.Context, updateModel *UpdateMergeRequestParams) error { + updateQuery := m.db.NewUpdate().Model((*MergeRequest)(nil)).Where("id = ?", updateModel.id) + if updateModel.description != nil { + updateQuery.Set("description = ?", *updateModel.description) + } + if updateModel.title != nil { + updateQuery.Set("title = ?", *updateModel.title) + } + _, err := updateQuery.Exec(ctx) + return err +} diff --git a/models/merge_request_test.go b/models/merge_request_test.go index 23b1da43..cf5ae109 100644 --- a/models/merge_request_test.go +++ b/models/merge_request_test.go @@ -3,6 +3,9 @@ package models_test import ( "context" "testing" + "time" + + "github.com/jiaozifs/jiaozifs/utils" "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" @@ -19,16 +22,81 @@ func TestMergeRequestRepoInsert(t *testing.T) { mrRepo := models.NewMergeRequestRepo(db) - mrModel := &models.MergeRequest{} - require.NoError(t, gofakeit.Struct(mrModel)) - newMrModel, err := mrRepo.Insert(ctx, mrModel) - require.NoError(t, err) - require.NotEqual(t, uuid.Nil, newMrModel.ID) + t.Run("insert and get", func(t *testing.T) { + mrModel := &models.MergeRequest{} + require.NoError(t, gofakeit.Struct(mrModel)) + newMrModel, err := mrRepo.Insert(ctx, mrModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newMrModel.ID) + + getMRParams := models.NewGetMergeRequestParams(). + SetID(newMrModel.ID) + mrModel, err = mrRepo.Get(ctx, getMRParams) + require.NoError(t, err) + + require.True(t, cmp.Equal(mrModel, newMrModel, dbTimeCmpOpt)) + }) + + t.Run("delete", func(t *testing.T) { + mrModel := &models.MergeRequest{} + require.NoError(t, gofakeit.Struct(mrModel)) + newMrModel, err := mrRepo.Insert(ctx, mrModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newMrModel.ID) + + affectRows, err := mrRepo.Delete(ctx, models.NewDeleteMergeRequestParams().SetID(newMrModel.ID)) + require.NoError(t, err) + require.Equal(t, int64(1), affectRows) + }) + + t.Run("list", func(t *testing.T) { + targetID := uuid.New() + for i := 0; i < 10; i++ { + mrModel := &models.MergeRequest{} + require.NoError(t, gofakeit.Struct(mrModel)) + mrModel.TargetRepoID = targetID + mrModel.UpdatedAt = time.Now() + mrModel.CreatedAt = time.Now() + newMrModel, err := mrRepo.Insert(ctx, mrModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newMrModel.ID) + } + + t.Run("first page", func(t *testing.T) { + mrs, hasMore, err := mrRepo.List(ctx, models.NewListMergeRequestParams().SetTargetRepoID(targetID).SetAmount(5)) + require.NoError(t, err) + require.True(t, hasMore) + require.Len(t, mrs, 5) + }) + t.Run("first page", func(t *testing.T) { + mrs, hasMore, err := mrRepo.List(ctx, models.NewListMergeRequestParams().SetTargetRepoID(targetID).SetAmount(5).SetAfter(time.Now().Add(time.Second*3))) + require.NoError(t, err) + require.False(t, hasMore) + require.Len(t, mrs, 0) + }) + }) + + t.Run("updatebyid", func(t *testing.T) { + mrModel := &models.MergeRequest{} + require.NoError(t, gofakeit.Struct(mrModel)) + newMrModel, err := mrRepo.Insert(ctx, mrModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newMrModel.ID) + + newMrModel.Title = "Merge: xxxxx" + newMrModel.Description = utils.String("vvvv") + + updateMrParams := models.NewUpdateMergeRequestParams(newMrModel.ID).SetTitle("Merge: xxxx").SetDescription("test update") + + err = mrRepo.UpdateByID(ctx, updateMrParams) + require.NoError(t, err) - getMRParams := models.NewGetMergeRequestParams(). - SetID(newMrModel.ID) - mrModel, err = mrRepo.Get(ctx, getMRParams) - require.NoError(t, err) + getMRParams := models.NewGetMergeRequestParams(). + SetID(newMrModel.ID) + mrModel, err = mrRepo.Get(ctx, getMRParams) + require.NoError(t, err) - require.True(t, cmp.Equal(mrModel, newMrModel, dbTimeCmpOpt)) + require.Equal(t, "Merge: xxxx", mrModel.Title) + require.Equal(t, "test update", *mrModel.Description) + }) } diff --git a/models/repository.go b/models/repository.go index b68cd89b..80859c83 100644 --- a/models/repository.go +++ b/models/repository.go @@ -28,10 +28,10 @@ type Repository struct { } type GetRepoParams struct { - ID uuid.UUID - CreatorID uuid.UUID - OwnerID uuid.UUID - Name *string + id uuid.UUID + creatorID uuid.UUID + ownerID uuid.UUID + name *string } func NewGetRepoParams() *GetRepoParams { @@ -39,33 +39,33 @@ func NewGetRepoParams() *GetRepoParams { } func (gup *GetRepoParams) SetID(id uuid.UUID) *GetRepoParams { - gup.ID = id + gup.id = id return gup } func (gup *GetRepoParams) SetOwnerID(id uuid.UUID) *GetRepoParams { - gup.OwnerID = id + gup.ownerID = id return gup } func (gup *GetRepoParams) SetCreatorID(creatorID uuid.UUID) *GetRepoParams { - gup.CreatorID = creatorID + gup.creatorID = creatorID return gup } func (gup *GetRepoParams) SetName(name string) *GetRepoParams { - gup.Name = &name + gup.name = &name return gup } type ListRepoParams struct { - ID uuid.UUID - CreatorID uuid.UUID - OwnerID uuid.UUID - Name *string - NameMatch MatchMode - After *time.Time - Amount int + id uuid.UUID + creatorID uuid.UUID + ownerID uuid.UUID + name *string + nameMatch MatchMode + after *time.Time + amount int } func NewListRepoParams() *ListRepoParams { @@ -73,39 +73,39 @@ func NewListRepoParams() *ListRepoParams { } func (lrp *ListRepoParams) SetID(id uuid.UUID) *ListRepoParams { - lrp.ID = id + lrp.id = id return lrp } func (lrp *ListRepoParams) SetOwnerID(ownerID uuid.UUID) *ListRepoParams { - lrp.OwnerID = ownerID + lrp.ownerID = ownerID return lrp } func (lrp *ListRepoParams) SetName(name string, match MatchMode) *ListRepoParams { - lrp.Name = &name - lrp.NameMatch = match + lrp.name = &name + lrp.nameMatch = match return lrp } func (lrp *ListRepoParams) SetCreatorID(creatorID uuid.UUID) *ListRepoParams { - lrp.CreatorID = creatorID + lrp.creatorID = creatorID return lrp } func (lrp *ListRepoParams) SetAfter(after time.Time) *ListRepoParams { - lrp.After = &after + lrp.after = &after return lrp } func (lrp *ListRepoParams) SetAmount(amount int) *ListRepoParams { - lrp.Amount = amount + lrp.amount = amount return lrp } type DeleteRepoParams struct { - ID uuid.UUID - OwnerID uuid.UUID - Name *string + id uuid.UUID + ownerID uuid.UUID + name *string } func NewDeleteRepoParams() *DeleteRepoParams { @@ -113,40 +113,39 @@ func NewDeleteRepoParams() *DeleteRepoParams { } func (drp *DeleteRepoParams) SetID(id uuid.UUID) *DeleteRepoParams { - drp.ID = id + drp.id = id return drp } func (drp *DeleteRepoParams) SetOwnerID(ownerID uuid.UUID) *DeleteRepoParams { - drp.OwnerID = ownerID + drp.ownerID = ownerID return drp } func (drp *DeleteRepoParams) SetName(name string) *DeleteRepoParams { - drp.Name = &name + drp.name = &name return drp } type UpdateRepoParams struct { - bun.BaseModel `bun:"table:repositories"` - ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` - Description *string `bun:"description"` - HEAD *string `bun:"head,notnull"` + id uuid.UUID + description *string + head *string } func NewUpdateRepoParams(id uuid.UUID) *UpdateRepoParams { return &UpdateRepoParams{ - ID: id, + id: id, } } func (up *UpdateRepoParams) SetDescription(description string) *UpdateRepoParams { - up.Description = &description + up.description = &description return up } func (up *UpdateRepoParams) SetHead(head string) *UpdateRepoParams { - up.HEAD = &head + up.head = &head return up } @@ -181,20 +180,20 @@ func (r *RepositoryRepo) Get(ctx context.Context, params *GetRepoParams) (*Repos repo := &Repository{} query := r.db.NewSelect().Model(repo) - if uuid.Nil != params.ID { - query = query.Where("id = ?", params.ID) + if uuid.Nil != params.id { + query = query.Where("id = ?", params.id) } - if uuid.Nil != params.CreatorID { - query = query.Where("creator_id = ?", params.CreatorID) + if uuid.Nil != params.creatorID { + query = query.Where("creator_id = ?", params.creatorID) } - if uuid.Nil != params.OwnerID { - query = query.Where("owner_id = ?", params.OwnerID) + if uuid.Nil != params.ownerID { + query = query.Where("owner_id = ?", params.ownerID) } - if params.Name != nil { - query = query.Where("name = ?", *params.Name) + if params.name != nil { + query = query.Where("name = ?", *params.name) } err := query.Limit(1).Scan(ctx) @@ -208,48 +207,48 @@ func (r *RepositoryRepo) List(ctx context.Context, params *ListRepoParams) ([]*R repos := []*Repository{} query := r.db.NewSelect().Model(&repos) - if uuid.Nil != params.CreatorID { - query = query.Where("creator_id = ?", params.CreatorID) + if uuid.Nil != params.creatorID { + query = query.Where("creator_id = ?", params.creatorID) } - if uuid.Nil != params.OwnerID { - query = query.Where("owner_id = ?", params.OwnerID) + if uuid.Nil != params.ownerID { + query = query.Where("owner_id = ?", params.ownerID) } - if params.Name != nil { - switch params.NameMatch { + if params.name != nil { + switch params.nameMatch { case ExactMatch: - query = query.Where("name = ?", *params.Name) + query = query.Where("name = ?", *params.name) case PrefixMatch: - query = query.Where("name LIKE ?", *params.Name+"%") + query = query.Where("name LIKE ?", *params.name+"%") case SuffixMatch: - query = query.Where("name LIKE ?", "%"+*params.Name) + query = query.Where("name LIKE ?", "%"+*params.name) case LikeMatch: - query = query.Where("name LIKE ?", "%"+*params.Name+"%") + query = query.Where("name LIKE ?", "%"+*params.name+"%") } } query = query.Order("updated_at DESC") - if params.After != nil { - query = query.Where("updated_at < ?", *params.After) + if params.after != nil { + query = query.Where("updated_at < ?", *params.after) } - err := query.Limit(params.Amount).Scan(ctx) - return repos, len(repos) == params.Amount, err + err := query.Limit(params.amount).Scan(ctx) + return repos, len(repos) == params.amount, err } func (r *RepositoryRepo) Delete(ctx context.Context, params *DeleteRepoParams) (int64, error) { query := r.db.NewDelete().Model((*Repository)(nil)) - if uuid.Nil != params.ID { - query = query.Where("id = ?", params.ID) + if uuid.Nil != params.id { + query = query.Where("id = ?", params.id) } - if params.Name != nil { - query = query.Where("name = ?", params.Name) + if params.name != nil { + query = query.Where("name = ?", params.name) } - if uuid.Nil != params.OwnerID { - query = query.Where("owner_id = ?", params.OwnerID) + if uuid.Nil != params.ownerID { + query = query.Where("owner_id = ?", params.ownerID) } sqlResult, err := query.Exec(ctx) @@ -264,12 +263,12 @@ func (r *RepositoryRepo) Delete(ctx context.Context, params *DeleteRepoParams) ( } func (r *RepositoryRepo) UpdateByID(ctx context.Context, updateModel *UpdateRepoParams) error { - updateQuery := r.db.NewUpdate().Model((*Repository)(nil)).Where("id = ?", updateModel.ID) - if updateModel.Description != nil { - updateQuery.Set("description = ?", *updateModel.Description) + updateQuery := r.db.NewUpdate().Model((*Repository)(nil)).Where("id = ?", updateModel.id) + if updateModel.description != nil { + updateQuery.Set("description = ?", *updateModel.description) } - if updateModel.HEAD != nil { - updateQuery.Set("head = ?", *updateModel.HEAD) + if updateModel.head != nil { + updateQuery.Set("head = ?", *updateModel.head) } _, err := updateQuery.Exec(ctx) return err diff --git a/models/tree.go b/models/tree.go index 02c7563e..51f1f97d 100644 --- a/models/tree.go +++ b/models/tree.go @@ -262,7 +262,7 @@ func (fileTree *FileTree) TreeNode() *TreeNode { } type GetObjParams struct { - Hash hash.Hash + hash hash.Hash } func NewGetObjParams() *GetObjParams { @@ -270,7 +270,7 @@ func NewGetObjParams() *GetObjParams { } func (gop *GetObjParams) SetHash(hash hash.Hash) *GetObjParams { - gop.Hash = hash + gop.hash = hash return gop } @@ -331,8 +331,8 @@ func (o FileTreeRepo) Get(ctx context.Context, params *GetObjParams) (*FileTree, repo := &FileTree{} query := o.db.NewSelect().Model(repo).Where("repository_id = ?", o.repositoryID) - if params.Hash != nil { - query = query.Where("hash = ?", params.Hash) + if params.hash != nil { + query = query.Where("hash = ?", params.hash) } err := query.Limit(1).Scan(ctx, repo) diff --git a/models/user.go b/models/user.go index 62ee9301..8ee40aeb 100644 --- a/models/user.go +++ b/models/user.go @@ -23,9 +23,9 @@ type User struct { } type GetUserParams struct { - ID uuid.UUID - Name *string - Email *string + id uuid.UUID + name *string + email *string } func NewGetUserParams() *GetUserParams { @@ -33,17 +33,17 @@ func NewGetUserParams() *GetUserParams { } func (gup *GetUserParams) SetID(id uuid.UUID) *GetUserParams { - gup.ID = id + gup.id = id return gup } func (gup *GetUserParams) SetName(name string) *GetUserParams { - gup.Name = &name + gup.name = &name return gup } func (gup *GetUserParams) SetEmail(email string) *GetUserParams { - gup.Email = &email + gup.email = &email return gup } @@ -74,16 +74,16 @@ func (userRepo *UserRepo) Get(ctx context.Context, params *GetUserParams) (*User user := &User{} query := userRepo.db.NewSelect().Model(user) - if uuid.Nil != params.ID { - query = query.Where("id = ?", params.ID) + if uuid.Nil != params.id { + query = query.Where("id = ?", params.id) } - if params.Name != nil { - query = query.Where("name = ?", params.Name) + if params.name != nil { + query = query.Where("name = ?", params.name) } - if params.Email != nil { - query = query.Where("email = ?", *params.Email) + if params.email != nil { + query = query.Where("email = ?", *params.email) } err := query.Limit(1).Scan(ctx) @@ -96,16 +96,16 @@ func (userRepo *UserRepo) Get(ctx context.Context, params *GetUserParams) (*User func (userRepo *UserRepo) Count(ctx context.Context, params *GetUserParams) (int, error) { query := userRepo.db.NewSelect().Model((*User)(nil)) - if uuid.Nil != params.ID { - query = query.Where("id = ?", params.ID) + if uuid.Nil != params.id { + query = query.Where("id = ?", params.id) } - if params.Name != nil { - query = query.Where("name = ?", params.Name) + if params.name != nil { + query = query.Where("name = ?", params.name) } - if params.Email != nil { - query = query.Where("email = ?", *params.Email) + if params.email != nil { + query = query.Where("email = ?", *params.email) } return query.Count(ctx) diff --git a/models/wip.go b/models/wip.go index fa551a95..dc2e049d 100644 --- a/models/wip.go +++ b/models/wip.go @@ -30,10 +30,10 @@ type WorkingInProcess struct { } type GetWipParams struct { - ID uuid.UUID - CreatorID uuid.UUID - RepositoryID uuid.UUID - RefID uuid.UUID + id uuid.UUID + creatorID uuid.UUID + repositoryID uuid.UUID + refID uuid.UUID } func NewGetWipParams() *GetWipParams { @@ -41,54 +41,54 @@ func NewGetWipParams() *GetWipParams { } func (gwp *GetWipParams) SetID(id uuid.UUID) *GetWipParams { - gwp.ID = id + gwp.id = id return gwp } func (gwp *GetWipParams) SetCreatorID(creatorID uuid.UUID) *GetWipParams { - gwp.CreatorID = creatorID + gwp.creatorID = creatorID return gwp } func (gwp *GetWipParams) SetRepositoryID(repositoryID uuid.UUID) *GetWipParams { - gwp.RepositoryID = repositoryID + gwp.repositoryID = repositoryID return gwp } func (gwp *GetWipParams) SetRefID(refID uuid.UUID) *GetWipParams { - gwp.RefID = refID + gwp.refID = refID return gwp } type ListWipParams struct { - CreatorID uuid.UUID - RepositoryID uuid.UUID - RefID uuid.UUID + creatorID uuid.UUID + repositoryID uuid.UUID + refID uuid.UUID } func NewListWipParams() *ListWipParams { return &ListWipParams{} } func (lwp *ListWipParams) SetCreatorID(creatorID uuid.UUID) *ListWipParams { - lwp.CreatorID = creatorID + lwp.creatorID = creatorID return lwp } func (lwp *ListWipParams) SetRepositoryID(repositoryID uuid.UUID) *ListWipParams { - lwp.RepositoryID = repositoryID + lwp.repositoryID = repositoryID return lwp } func (lwp *ListWipParams) SetRefID(refID uuid.UUID) *ListWipParams { - lwp.RefID = refID + lwp.refID = refID return lwp } type DeleteWipParams struct { - ID uuid.UUID - CreatorID uuid.UUID - RepositoryID uuid.UUID - RefID uuid.UUID + id uuid.UUID + creatorID uuid.UUID + repositoryID uuid.UUID + refID uuid.UUID } func NewDeleteWipParams() *DeleteWipParams { @@ -96,50 +96,49 @@ func NewDeleteWipParams() *DeleteWipParams { } func (dwp *DeleteWipParams) SetID(id uuid.UUID) *DeleteWipParams { - dwp.ID = id + dwp.id = id return dwp } func (dwp *DeleteWipParams) SetCreatorID(creatorID uuid.UUID) *DeleteWipParams { - dwp.CreatorID = creatorID + dwp.creatorID = creatorID return dwp } func (dwp *DeleteWipParams) SetRepositoryID(repositoryID uuid.UUID) *DeleteWipParams { - dwp.RepositoryID = repositoryID + dwp.repositoryID = repositoryID return dwp } func (dwp *DeleteWipParams) SetRefID(refID uuid.UUID) *DeleteWipParams { - dwp.RefID = refID + dwp.refID = refID return dwp } type UpdateWipParams struct { - bun.BaseModel `bun:"table:wips"` - ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` - CurrentTree hash.Hash `bun:"current_tree,type:bytea,notnull"` - BaseCommit hash.Hash `bun:"base_commit,type:bytea,notnull"` - State WipState `bun:"state,notnull"` - UpdatedAt time.Time `bun:"updated_at"` + id uuid.UUID + currentTree hash.Hash + baseCommit hash.Hash + state *WipState + updatedAt time.Time } func NewUpdateWipParams(id uuid.UUID) *UpdateWipParams { - return &UpdateWipParams{ID: id, UpdatedAt: time.Now()} + return &UpdateWipParams{id: id, updatedAt: time.Now()} } func (up *UpdateWipParams) SetCurrentTree(currentTree hash.Hash) *UpdateWipParams { - up.CurrentTree = currentTree + up.currentTree = currentTree return up } func (up *UpdateWipParams) SetBaseCommit(commitHash hash.Hash) *UpdateWipParams { - up.BaseCommit = commitHash + up.baseCommit = commitHash return up } func (up *UpdateWipParams) SetState(state WipState) *UpdateWipParams { - up.State = state + up.state = &state return up } @@ -174,20 +173,20 @@ func (s *WipRepo) Get(ctx context.Context, params *GetWipParams) (*WorkingInProc wips := &WorkingInProcess{} query := s.db.NewSelect().Model(wips) - if uuid.Nil != params.ID { - query = query.Where("id = ?", params.ID) + if uuid.Nil != params.id { + query = query.Where("id = ?", params.id) } - if uuid.Nil != params.CreatorID { - query = query.Where("creator_id = ?", params.CreatorID) + if uuid.Nil != params.creatorID { + query = query.Where("creator_id = ?", params.creatorID) } - if uuid.Nil != params.RepositoryID { - query = query.Where("repository_id = ?", params.RepositoryID) + if uuid.Nil != params.repositoryID { + query = query.Where("repository_id = ?", params.repositoryID) } - if uuid.Nil != params.RefID { - query = query.Where("ref_id = ?", params.RefID) + if uuid.Nil != params.refID { + query = query.Where("ref_id = ?", params.refID) } err := query.Limit(1).Scan(ctx) @@ -201,16 +200,16 @@ func (s *WipRepo) List(ctx context.Context, params *ListWipParams) ([]*WorkingIn var resp []*WorkingInProcess query := s.db.NewSelect().Model(&resp) - if uuid.Nil != params.CreatorID { - query = query.Where("creator_id = ?", params.CreatorID) + if uuid.Nil != params.creatorID { + query = query.Where("creator_id = ?", params.creatorID) } - if uuid.Nil != params.RepositoryID { - query = query.Where("repository_id = ?", params.RepositoryID) + if uuid.Nil != params.repositoryID { + query = query.Where("repository_id = ?", params.repositoryID) } - if uuid.Nil != params.RefID { - query = query.Where("ref_id = ?", params.RefID) + if uuid.Nil != params.refID { + query = query.Where("ref_id = ?", params.refID) } err := query.Scan(ctx) @@ -224,20 +223,20 @@ func (s *WipRepo) List(ctx context.Context, params *ListWipParams) ([]*WorkingIn func (s *WipRepo) Delete(ctx context.Context, params *DeleteWipParams) (int64, error) { query := s.db.NewDelete().Model((*WorkingInProcess)(nil)) - if uuid.Nil != params.CreatorID { - query = query.Where("creator_id = ?", params.CreatorID) + if uuid.Nil != params.creatorID { + query = query.Where("creator_id = ?", params.creatorID) } - if uuid.Nil != params.RepositoryID { - query = query.Where("repository_id = ?", params.RepositoryID) + if uuid.Nil != params.repositoryID { + query = query.Where("repository_id = ?", params.repositoryID) } - if uuid.Nil != params.RefID { - query = query.Where("ref_id = ?", params.RefID) + if uuid.Nil != params.refID { + query = query.Where("ref_id = ?", params.refID) } - if uuid.Nil != params.ID { - query = query.Where("id = ?", params.ID) + if uuid.Nil != params.id { + query = query.Where("id = ?", params.id) } r, err := query.Exec(ctx) if err != nil { @@ -251,6 +250,22 @@ func (s *WipRepo) Delete(ctx context.Context, params *DeleteWipParams) (int64, e } func (s *WipRepo) UpdateByID(ctx context.Context, updateModel *UpdateWipParams) error { - _, err := s.db.NewUpdate().Model(updateModel).WherePK().OmitZero().Exec(ctx) + updateQuery := s.db.NewUpdate(). + Model((*WorkingInProcess)(nil)). + Where("id = ?", updateModel.id). + Set("updated_at = ?", updateModel.updatedAt) + + if updateModel.state != nil { + updateQuery.Set("state = ?", *updateModel.state) + } + + if updateModel.currentTree != nil { + updateQuery.Set("current_tree = ?", updateModel.currentTree) + } + + if updateModel.baseCommit != nil { + updateQuery.Set("base_commit = ?", updateModel.baseCommit) + } + _, err := updateQuery.Exec(ctx) return err } diff --git a/utils/hash/hash.go b/utils/hash/hash.go index c30f72bd..e2458427 100644 --- a/utils/hash/hash.go +++ b/utils/hash/hash.go @@ -12,20 +12,20 @@ import ( var _ json.Marshaler = (*Hash)(nil) var _ json.Unmarshaler = (*Hash)(nil) -var EmptyHash = Hash{} +var Empty = Hash{} type Hash []byte func FromHex(str string) (Hash, error) { if len(str) == 0 { - return EmptyHash, nil + return Empty, nil } return hex.DecodeString(str) } func (hash Hash) Hex() string { if hash == nil { - return hex.EncodeToString(EmptyHash) + return hex.EncodeToString(Empty) } return hex.EncodeToString(hash) } diff --git a/utils/hash/hash_test.go b/utils/hash/hash_test.go index fd8f5ea6..782b85b2 100644 --- a/utils/hash/hash_test.go +++ b/utils/hash/hash_test.go @@ -49,7 +49,7 @@ func TestHashFromHex(t *testing.T) { t.Run("empty", func(t *testing.T) { hash, err := FromHex("") require.NoError(t, err) - require.Equal(t, EmptyHash, hash) + require.Equal(t, Empty, hash) }) t.Run("data", func(t *testing.T) { diff --git a/versionmgr/changes.go b/versionmgr/changes.go index c1c9f075..319ed614 100644 --- a/versionmgr/changes.go +++ b/versionmgr/changes.go @@ -126,29 +126,29 @@ func newChanges(mChanges merkletrie.Changes) *Changes { } type ChangePair struct { - Base IChange - Merge IChange + Left IChange + Right IChange IsConflict bool } type ChangesPairIter struct { - BaseChanges *Changes - MergerChanges *Changes + leftChanges *Changes + rightChanges *Changes } func NewChangesPairIter(baseChanges *Changes, mergerChanges *Changes) *ChangesPairIter { - return &ChangesPairIter{BaseChanges: baseChanges, MergerChanges: mergerChanges} + return &ChangesPairIter{leftChanges: baseChanges, rightChanges: mergerChanges} } // Has check if any changes func (cw *ChangesPairIter) Has() bool { - return cw.BaseChanges.Has() || cw.MergerChanges.Has() + return cw.leftChanges.Has() || cw.rightChanges.Has() } // Reset reset changes func (cw *ChangesPairIter) Reset() { - cw.BaseChanges.Reset() - cw.MergerChanges.Reset() + cw.leftChanges.Reset() + cw.rightChanges.Reset() } // Next find change file, first sort each file in change, pop files from two changes, compare filename, @@ -158,12 +158,12 @@ func (cw *ChangesPairIter) Reset() { // both file name match, try to resolve file changes // if one of the queue consume up, pick left change in the other queue func (cw *ChangesPairIter) Next() (*ChangePair, error) { - baseNode, baseErr := cw.BaseChanges.Next() + baseNode, baseErr := cw.leftChanges.Next() if baseErr != nil && baseErr != io.EOF { return nil, baseErr } - mergeNode, mergerError := cw.MergerChanges.Next() + mergeNode, mergerError := cw.rightChanges.Next() if mergerError != nil && mergerError != io.EOF { return nil, mergerError } @@ -173,19 +173,19 @@ func (cw *ChangesPairIter) Next() (*ChangePair, error) { } if baseErr == io.EOF { - return &ChangePair{Merge: mergeNode}, nil + return &ChangePair{Right: mergeNode}, nil } if mergerError == io.EOF { - return &ChangePair{Base: baseNode}, nil + return &ChangePair{Left: baseNode}, nil } compare := strings.Compare(baseNode.Path(), mergeNode.Path()) if compare > 0 { //only merger change - cw.BaseChanges.Back() + cw.leftChanges.Back() return &ChangePair{ - Merge: mergeNode, + Right: mergeNode, }, nil } else if compare == 0 { isConflict, err := cw.isConflict(baseNode, mergeNode) @@ -193,15 +193,15 @@ func (cw *ChangesPairIter) Next() (*ChangePair, error) { return nil, err } return &ChangePair{ - Base: baseNode, - Merge: mergeNode, + Left: baseNode, + Right: mergeNode, IsConflict: isConflict, }, nil } //only base change - cw.MergerChanges.Back() + cw.rightChanges.Back() return &ChangePair{ - Base: baseNode, + Left: baseNode, }, nil } @@ -287,12 +287,12 @@ func (cw *ChangesMergeIter) Next() (IChange, error) { } if chPair.IsConflict { - return cw.resolveConflict(chPair.Base, chPair.Merge) + return cw.resolveConflict(chPair.Left, chPair.Right) } - if chPair.Base != nil { - return chPair.Base, nil + if chPair.Left != nil { + return chPair.Left, nil } - return chPair.Merge, nil + return chPair.Right, nil } func (cw *ChangesMergeIter) resolveConflict(base, merge IChange) (IChange, error) { diff --git a/versionmgr/commit_node_test.go b/versionmgr/commit_node_test.go index fdbe6c69..ce96da65 100644 --- a/versionmgr/commit_node_test.go +++ b/versionmgr/commit_node_test.go @@ -27,30 +27,30 @@ func TestWrapCommitNode(t *testing.T) { repoID := uuid.New() repo := models.NewRepo(db) - rootCommit, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "root") + rootCommit, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "root") require.NoError(t, err) - commitA, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit a", rootCommit.Hash) + commitA, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit a", rootCommit.Hash) require.NoError(t, err) - commitB, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commt b", commitA.Hash) + commitB, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commt b", commitA.Hash) require.NoError(t, err) - commitC, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit c", rootCommit.Hash) + commitC, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit c", rootCommit.Hash) require.NoError(t, err) - commitD, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit d", commitC.Hash) + commitD, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit d", commitC.Hash) require.NoError(t, err) - commitE, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit e", commitB.Hash, commitD.Hash) + commitE, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit e", commitB.Hash, commitD.Hash) require.NoError(t, err) - commitF, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit f", commitE.Hash) + commitF, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit f", commitE.Hash) require.NoError(t, err) commitFNode := NewWrapCommitNode(repo.CommitRepo(repoID), commitF) require.Equal(t, commitF.Hash.Hex(), commitFNode.Commit().Hash.Hex()) require.Equal(t, repoID, commitFNode.RepoID()) - require.Equal(t, hash.EmptyHash, commitFNode.TreeHash()) + require.Equal(t, hash.Empty, commitFNode.TreeHash()) require.Equal(t, commitF.Hash.Hex(), commitFNode.Hash().Hex()) t.Run("parent", func(t *testing.T) { diff --git a/versionmgr/commit_walker_bfs_filtered_test.go b/versionmgr/commit_walker_bfs_filtered_test.go index 757d1592..22eca2ab 100644 --- a/versionmgr/commit_walker_bfs_filtered_test.go +++ b/versionmgr/commit_walker_bfs_filtered_test.go @@ -20,23 +20,23 @@ func TestNewFilterCommitIter(t *testing.T) { repoID := uuid.New() repo := models.NewRepo(db) - rootCommit, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "root") + rootCommit, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "root") require.NoError(t, err) - commitA, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit a", rootCommit.Hash) + commitA, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit a", rootCommit.Hash) require.NoError(t, err) - commitB, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commt b", commitA.Hash) + commitB, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commt b", commitA.Hash) require.NoError(t, err) - commitC, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit c", rootCommit.Hash) + commitC, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit c", rootCommit.Hash) require.NoError(t, err) - commitD, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit d", commitC.Hash) + commitD, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit d", commitC.Hash) require.NoError(t, err) - commitE, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit e", commitB.Hash, commitD.Hash) + commitE, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit e", commitB.Hash, commitD.Hash) require.NoError(t, err) - commitF, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit f", commitE.Hash) + commitF, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit f", commitE.Hash) require.NoError(t, err) commitFNode := NewWrapCommitNode(repo.CommitRepo(repoID), commitF) diff --git a/versionmgr/commit_walker_bfs_test.go b/versionmgr/commit_walker_bfs_test.go index 29418e7c..b68d1e79 100644 --- a/versionmgr/commit_walker_bfs_test.go +++ b/versionmgr/commit_walker_bfs_test.go @@ -27,23 +27,23 @@ func TestBfsCommitIterator(t *testing.T) { repoID := uuid.New() repo := models.NewRepo(db) - rootCommit, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "root") + rootCommit, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "root") require.NoError(t, err) - commitA, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit a", rootCommit.Hash) + commitA, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit a", rootCommit.Hash) require.NoError(t, err) - commitB, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commt b", commitA.Hash) + commitB, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commt b", commitA.Hash) require.NoError(t, err) - commitC, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit c", rootCommit.Hash) + commitC, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit c", rootCommit.Hash) require.NoError(t, err) - commitD, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit d", commitC.Hash) + commitD, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit d", commitC.Hash) require.NoError(t, err) - commitE, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit e", commitB.Hash, commitD.Hash) + commitE, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit e", commitB.Hash, commitD.Hash) require.NoError(t, err) - commitF, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit f", commitE.Hash) + commitF, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit f", commitE.Hash) require.NoError(t, err) commitFNode := NewWrapCommitNode(repo.CommitRepo(repoID), commitF) diff --git a/versionmgr/commit_walker_ctime_test.go b/versionmgr/commit_walker_ctime_test.go index 5dc5f09d..a52362fe 100644 --- a/versionmgr/commit_walker_ctime_test.go +++ b/versionmgr/commit_walker_ctime_test.go @@ -30,31 +30,31 @@ func TestNewCommitIterCTime(t *testing.T) { repoID := uuid.New() repo := models.NewRepo(db) - rootCommit, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "root") + rootCommit, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "root") require.NoError(t, err) time.Sleep(time.Millisecond * 500) - commitA, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit a", rootCommit.Hash) + commitA, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit a", rootCommit.Hash) require.NoError(t, err) time.Sleep(time.Millisecond * 500) - commitC, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit c", rootCommit.Hash) + commitC, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit c", rootCommit.Hash) require.NoError(t, err) time.Sleep(time.Millisecond * 500) - commitB, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commt b", commitA.Hash) + commitB, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commt b", commitA.Hash) require.NoError(t, err) time.Sleep(time.Millisecond * 500) - commitD, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit d", commitC.Hash) + commitD, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit d", commitC.Hash) require.NoError(t, err) time.Sleep(time.Millisecond * 500) - commitE, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit e", commitB.Hash, commitD.Hash) + commitE, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit e", commitB.Hash, commitD.Hash) require.NoError(t, err) time.Sleep(time.Millisecond * 500) - commitF, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit f", commitE.Hash) + commitF, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit f", commitE.Hash) require.NoError(t, err) commitFNode := NewWrapCommitNode(repo.CommitRepo(repoID), commitF) diff --git a/versionmgr/commit_walker_test.go b/versionmgr/commit_walker_test.go index fa1c88a8..dc50422b 100644 --- a/versionmgr/commit_walker_test.go +++ b/versionmgr/commit_walker_test.go @@ -27,23 +27,23 @@ func TestNewCommitPostorderIter(t *testing.T) { repoID := uuid.New() repo := models.NewRepo(db) - rootCommit, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "root") + rootCommit, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "root") require.NoError(t, err) - commitA, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit a", rootCommit.Hash) + commitA, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit a", rootCommit.Hash) require.NoError(t, err) - commitB, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commt b", commitA.Hash) + commitB, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commt b", commitA.Hash) require.NoError(t, err) - commitC, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit c", rootCommit.Hash) + commitC, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit c", rootCommit.Hash) require.NoError(t, err) - commitD, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit d", commitC.Hash) + commitD, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit d", commitC.Hash) require.NoError(t, err) - commitE, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit e", commitB.Hash, commitD.Hash) + commitE, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit e", commitB.Hash, commitD.Hash) require.NoError(t, err) - commitF, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit f", commitE.Hash) + commitF, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit f", commitE.Hash) require.NoError(t, err) commitFNode := NewWrapCommitNode(repo.CommitRepo(repoID), commitF) @@ -130,23 +130,23 @@ func TestCommitPreorderIter(t *testing.T) { repoID := uuid.New() repo := models.NewRepo(db) - rootCommit, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "root") + rootCommit, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "root") require.NoError(t, err) - commitA, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit a", rootCommit.Hash) + commitA, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit a", rootCommit.Hash) require.NoError(t, err) - commitB, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commt b", commitA.Hash) + commitB, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commt b", commitA.Hash) require.NoError(t, err) - commitC, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit c", rootCommit.Hash) + commitC, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit c", rootCommit.Hash) require.NoError(t, err) - commitD, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit d", commitC.Hash) + commitD, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit d", commitC.Hash) require.NoError(t, err) - commitE, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit e", commitB.Hash, commitD.Hash) + commitE, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit e", commitB.Hash, commitD.Hash) require.NoError(t, err) - commitF, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.EmptyHash, "commit f", commitE.Hash) + commitF, err := makeCommit(ctx, repo.CommitRepo(repoID), hash.Empty, "commit f", commitE.Hash) require.NoError(t, err) commitFNode := NewWrapCommitNode(repo.CommitRepo(repoID), commitF) diff --git a/versionmgr/conflict.go b/versionmgr/conflict.go index 5c27ecb9..c1aac846 100644 --- a/versionmgr/conflict.go +++ b/versionmgr/conflict.go @@ -76,7 +76,7 @@ func ConflictCollector() ConflictResolver { if err != nil { return nil, err } - baseHash := hash.EmptyHash + baseHash := hash.Empty if baseAct != merkletrie.Delete { baseHash = base.To().Hash() } @@ -84,7 +84,7 @@ func ConflictCollector() ConflictResolver { if err != nil { return nil, err } - mergeHash := hash.EmptyHash + mergeHash := hash.Empty if mergeAct != merkletrie.Delete { mergeHash = merge.To().Hash() } diff --git a/versionmgr/work_repo.go b/versionmgr/work_repo.go index 7a986678..2ef4c501 100644 --- a/versionmgr/work_repo.go +++ b/versionmgr/work_repo.go @@ -152,7 +152,7 @@ func (repository *WorkRepository) rootTree(ctx context.Context, repo models.IRep } repository.branch = ref - treeHash := hash.EmptyHash + treeHash := hash.Empty var commit *models.Commit if !ref.CommitHash.IsEmpty() { commit, err = repo.CommitRepo(repository.repoModel.ID).Commit(ctx, ref.CommitHash) @@ -168,7 +168,7 @@ func (repository *WorkRepository) rootTree(ctx context.Context, repo models.IRep } func (repository *WorkRepository) CheckOut(ctx context.Context, refType WorkRepoState, refName string) error { - treeHash := hash.EmptyHash + treeHash := hash.Empty if refType == InWip { ref, err := repository.repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.repoModel.ID).SetName(refName)) if err != nil { @@ -221,7 +221,7 @@ func (repository *WorkRepository) Revert(ctx context.Context, prefixPath string) return fmt.Errorf("working repo not in wip state") } - baseTreeHash := hash.EmptyHash + baseTreeHash := hash.Empty if !repository.wip.BaseCommit.IsEmpty() { commit, err := repository.repo.CommitRepo(repository.repoModel.ID).Commit(ctx, repository.wip.BaseCommit) if err != nil { @@ -490,7 +490,7 @@ func (repository *WorkRepository) CreateBranch(ctx context.Context, branchName s return nil, err } - commitHash := hash.EmptyHash + commitHash := hash.Empty if repository.commit != nil { commitHash = repository.commit.Hash } @@ -539,7 +539,7 @@ func (repository *WorkRepository) GetOrCreateWip(ctx context.Context) (*models.W } // if not found create a wip - currentTreeHash := hash.EmptyHash + currentTreeHash := hash.Empty if !repository.branch.CommitHash.IsEmpty() { baseCommit, err := repository.repo.CommitRepo(repository.repoModel.ID).Commit(ctx, repository.branch.CommitHash) if err != nil { @@ -587,14 +587,14 @@ func (repository *WorkRepository) GetCommitChanges(ctx context.Context, pathPref if err != nil { return nil, err } - return workTree.Diff(ctx, hash.EmptyHash, pathPrefix) + return workTree.Diff(ctx, hash.Empty, pathPrefix) } else if len(repository.commit.ParentHashes) == 1 { return repository.DiffCommit(ctx, repository.commit.ParentHashes[0], pathPrefix) } return repository.DiffCommit(ctx, repository.commit.ParentHashes[1], pathPrefix) } -func (repository *WorkRepository) GetMergeState(ctx context.Context, merger *models.User, toMergeCommitHash hash.Hash) ([]*ChangePair, error) { +func (repository *WorkRepository) GetMergeState(ctx context.Context, toMergeCommitHash hash.Hash) ([]*ChangePair, error) { if repository.state != InBranch { return nil, errors.New("must merge on branch") } @@ -620,7 +620,7 @@ func (repository *WorkRepository) GetMergeState(ctx context.Context, merger *mod commitRepo := repo.CommitRepo(repository.repoModel.ID) fileTreeRepo := repo.FileTreeRepo(repository.repoModel.ID) var err error - bestAncestor, err = findBestAncestor(ctx, commitRepo, fileTreeRepo, merger, repository.repoModel, commit, toMergeCommit) + bestAncestor, err = findBestAncestor(ctx, commitRepo, fileTreeRepo, repository.operator, repository.repoModel, commit, toMergeCommit) if err != nil { return err } @@ -636,12 +636,12 @@ func (repository *WorkRepository) GetMergeState(ctx context.Context, merger *mod return nil, err } - baseDiff, err := ancestorWorkTree.Diff(ctx, treeHashFromCommit(commit)) + baseDiff, err := ancestorWorkTree.Diff(ctx, treeHashFromCommit(commit), "") if err != nil { return nil, err } - mergeDiff, err := ancestorWorkTree.Diff(ctx, treeHashFromCommit(toMergeCommit)) + mergeDiff, err := ancestorWorkTree.Diff(ctx, treeHashFromCommit(toMergeCommit), "") if err != nil { return nil, err } @@ -659,7 +659,7 @@ func (repository *WorkRepository) GetMergeState(ctx context.Context, merger *mod } // Merge implement merge like git, docs https://en.wikipedia.org/wiki/Merge_(version_control) -func (repository *WorkRepository) Merge(ctx context.Context, merger *models.User, toMergeCommitHash hash.Hash, msg string, resolver ConflictResolver) (*models.Commit, error) { +func (repository *WorkRepository) Merge(ctx context.Context, toMergeCommitHash hash.Hash, msg string, resolver ConflictResolver) (*models.Commit, error) { if repository.state != InBranch { return nil, errors.New("must merge on branch") } @@ -684,11 +684,11 @@ func (repository *WorkRepository) Merge(ctx context.Context, merger *models.User err = repository.repo.Transaction(ctx, func(repo models.IRepo) error { commitRepo := repo.CommitRepo(repository.repoModel.ID) fileTreeRepo := repo.FileTreeRepo(repository.repoModel.ID) - bestAncestor, err := findBestAncestor(ctx, commitRepo, fileTreeRepo, merger, repository.repoModel, commit, toMergeCommit) + bestAncestor, err := findBestAncestor(ctx, commitRepo, fileTreeRepo, repository.operator, repository.repoModel, commit, toMergeCommit) if err != nil { return err } - newCommit, err = merge(ctx, commitRepo, fileTreeRepo, repository.repoModel, merger, bestAncestor, commit, toMergeCommit, msg, resolver) + newCommit, err = merge(ctx, commitRepo, fileTreeRepo, repository.repoModel, repository.operator, bestAncestor, commit, toMergeCommit, msg, resolver) if err != nil { return err } @@ -744,32 +744,6 @@ func findBestAncestor(ctx context.Context, baseCommitNode := NewWrapCommitNode(commitRepo, baseCommit) toMergeCommitNode := NewWrapCommitNode(commitRepo, toMergeCommit) - { - //do nothing while merge is ancestor of base - mergeIsAncestorOfBase, err := toMergeCommitNode.IsAncestor(ctx, baseCommitNode) - if err != nil { - return nil, err - } - - if mergeIsAncestorOfBase { - workRepoLog.Warnf("merge commit %s is ancestor of base commit %s", toMergeCommit.Hash, baseCommit.Hash) - return baseCommit, nil - } - } - - { - //try fast-forward merge no need to create new commit node - baseIsAncestorOfMerge, err := baseCommitNode.IsAncestor(ctx, toMergeCommitNode) - if err != nil { - return nil, err - } - - if baseIsAncestorOfMerge { - workRepoLog.Warnf("base commit %s is ancestor of merge commit %s", toMergeCommit.Hash, baseCommit.Hash) - return toMergeCommit, nil - } - } - // three-way merge bestAncestor, err := baseCommitNode.MergeBase(ctx, toMergeCommitNode) if err != nil { @@ -784,6 +758,10 @@ func findBestAncestor(ctx context.Context, if len(bestAncestor) > 1 { //merge cross merge create virtual commit subBestAncestor, err := findBestAncestor(ctx, commitRepo, fileTreeRepo, merger, repoModel, bestAncestor[0].Commit(), bestAncestor[1].Commit()) + if err != nil { + return nil, err + } + virtualCommit, err := merge(ctx, commitRepo, fileTreeRepo, repoModel, merger, subBestAncestor, bestAncestor[0].Commit(), bestAncestor[1].Commit(), "virtual commit", ForbidResolver) if err != nil { return nil, err @@ -819,6 +797,35 @@ func merge(ctx context.Context, return toMergeCommit, nil } + baseCommitNode := NewWrapCommitNode(commitRepo, baseCommit) + toMergeCommitNode := NewWrapCommitNode(commitRepo, toMergeCommit) + + { + //do nothing while merge is ancestor of base + mergeIsAncestorOfBase, err := toMergeCommitNode.IsAncestor(ctx, baseCommitNode) + if err != nil { + return nil, err + } + + if mergeIsAncestorOfBase { + workRepoLog.Warnf("merge commit %s is ancestor of base commit %s", toMergeCommit.Hash, baseCommit.Hash) + return baseCommit, nil + } + } + + { + //try fast-forward merge no need to create new commit node + baseIsAncestorOfMerge, err := baseCommitNode.IsAncestor(ctx, toMergeCommitNode) + if err != nil { + return nil, err + } + + if baseIsAncestorOfMerge { + workRepoLog.Warnf("base commit %s is ancestor of merge commit %s", toMergeCommit.Hash, baseCommit.Hash) + return toMergeCommit, nil + } + } + ancestorWorkTree, err := NewWorkTree(ctx, fileTreeRepo, models.NewRootTreeEntry(bestAncestor.TreeHash)) if err != nil { return nil, err @@ -887,12 +894,12 @@ func treeHashFromCommit(commit *models.Commit) hash.Hash { if commit != nil { return commit.TreeHash } - return hash.EmptyHash + return hash.Empty } -func commitHashFromCommit(commit *models.Commit) hash.Hash { +func commitHashFromCommit(commit *models.Commit) hash.Hash { //nolint if commit != nil { return commit.Hash } - return hash.EmptyHash + return hash.Empty } diff --git a/versionmgr/work_repo_diff_test.go b/versionmgr/work_repo_diff_test.go index 3705f703..48412059 100644 --- a/versionmgr/work_repo_diff_test.go +++ b/versionmgr/work_repo_diff_test.go @@ -36,7 +36,7 @@ func TestWorkRepositoryDiffCommit(t *testing.T) { workRepo := NewWorkRepositoryFromAdapter(ctx, user, project, repo, adapter) //base branch - err = workRepo.CheckOut(ctx, InCommit, hash.EmptyHash.Hex()) + err = workRepo.CheckOut(ctx, InCommit, hash.Empty.Hex()) require.NoError(t, err) _, err = workRepo.CreateBranch(ctx, "feat/base") require.NoError(t, err) diff --git a/versionmgr/work_repo_merge_test.go b/versionmgr/work_repo_merge_test.go index 76c102f8..a59e54f4 100644 --- a/versionmgr/work_repo_merge_test.go +++ b/versionmgr/work_repo_merge_test.go @@ -43,7 +43,7 @@ func TestCommitOpMerge(t *testing.T) { ` workRepo := NewWorkRepositoryFromAdapter(ctx, user, project, repo, adapter) - err = workRepo.CheckOut(ctx, InCommit, hash.EmptyHash.Hex()) + err = workRepo.CheckOut(ctx, InCommit, hash.Empty.Hex()) require.NoError(t, err) _, err = workRepo.CreateBranch(ctx, "feat/base") require.NoError(t, err) @@ -81,7 +81,7 @@ func TestCommitOpMerge(t *testing.T) { //--------------CommitAB require.NoError(t, workRepo.CheckOut(ctx, InBranch, "feat/branchA")) - commitAB, err := workRepo.Merge(ctx, user, commitB.Hash, "commit ab", LeastHashResolve) + commitAB, err := workRepo.Merge(ctx, commitB.Hash, "commit ab", LeastHashResolve) require.NoError(t, err) //--------------CommitF @@ -98,7 +98,7 @@ func TestCommitOpMerge(t *testing.T) { //commitC require.NoError(t, workRepo.CheckOut(ctx, InBranch, "feat/branchF")) - commitC, err := workRepo.Merge(ctx, user, commitA.Hash, "commit c", LeastHashResolve) + commitC, err := workRepo.Merge(ctx, commitA.Hash, "commit c", LeastHashResolve) require.NoError(t, err) //commitD @@ -124,20 +124,20 @@ func TestCommitOpMerge(t *testing.T) { //test fast-ward require.NoError(t, workRepo.CheckOut(ctx, InBranch, "feat/branchB")) - commitBE, err := workRepo.Merge(ctx, user, commitE.Hash, "commit ab", LeastHashResolve) + commitBE, err := workRepo.Merge(ctx, commitE.Hash, "commit ab", LeastHashResolve) require.NoError(t, err) require.Equal(t, commitE.Hash.Hex(), commitBE.Hash.Hex()) //commitG require.NoError(t, workRepo.CheckOut(ctx, InBranch, "feat/branchD_E")) - commitG, err := workRepo.Merge(ctx, user, commitAB.Hash, "commit g", LeastHashResolve) + commitG, err := workRepo.Merge(ctx, commitAB.Hash, "commit g", LeastHashResolve) require.NoError(t, err) _, err = makeBranch(ctx, repo.BranchRepo(), user, "feat/branchG", project.ID, commitG.Hash) require.NoError(t, err) require.NoError(t, workRepo.CheckOut(ctx, InBranch, "feat/branchG")) - _, err = workRepo.Merge(ctx, user, commitC.Hash, "commit cg", LeastHashResolve) + _, err = workRepo.Merge(ctx, commitC.Hash, "commit cg", LeastHashResolve) require.NoError(t, err) } @@ -172,7 +172,7 @@ func TestCrissCrossMerge(t *testing.T) { 1|b.txt |h2 ` - err = workRepo.CheckOut(ctx, InCommit, hash.EmptyHash.Hex()) + err = workRepo.CheckOut(ctx, InCommit, hash.Empty.Hex()) require.NoError(t, err) _, err = workRepo.CreateBranch(ctx, "feat/base") require.NoError(t, err) @@ -209,17 +209,17 @@ func TestCrissCrossMerge(t *testing.T) { //-----------------CommitAB require.NoError(t, workRepo.CheckOut(ctx, InBranch, "feat/branchB")) - commiyBC, err := workRepo.Merge(ctx, user, commitC.Hash, "commit bc", LeastHashResolve) + commiyBC, err := workRepo.Merge(ctx, commitC.Hash, "commit bc", LeastHashResolve) require.NoError(t, err) require.NoError(t, workRepo.CheckOut(ctx, InBranch, "feat/branchC")) - commitCB, err := workRepo.Merge(ctx, user, commitB.Hash, "commit cb", LeastHashResolve) + commitCB, err := workRepo.Merge(ctx, commitB.Hash, "commit cb", LeastHashResolve) require.NoError(t, err) _, err = makeBranch(ctx, repo.BranchRepo(), user, "feat/branchBC", project.ID, commiyBC.Hash) require.NoError(t, err) require.NoError(t, workRepo.CheckOut(ctx, InBranch, "feat/branchBC")) - _, err = workRepo.Merge(ctx, user, commitCB.Hash, "cross commit", LeastHashResolve) + _, err = workRepo.Merge(ctx, commitCB.Hash, "cross commit", LeastHashResolve) require.NoError(t, err) } diff --git a/versionmgr/work_repo_test.go b/versionmgr/work_repo_test.go index d2d32e32..1d9861c2 100644 --- a/versionmgr/work_repo_test.go +++ b/versionmgr/work_repo_test.go @@ -383,7 +383,7 @@ func makeRepository(ctx context.Context, repo models.IRepo, user *models.User, n } _, err = repo.BranchRepo().Insert(ctx, &models.Branch{ RepositoryID: repoModel.ID, - CommitHash: hash.EmptyHash, + CommitHash: hash.Empty, Name: "main", Description: nil, CreatedAt: time.Now(), From 174e1bcc2627348666abb8ecd770ec3ba9664061 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Tue, 9 Jan 2024 16:42:20 +0800 Subject: [PATCH 139/210] feat: add test for api --- api/jiaozifs.gen.go | 1101 ++++++++++++++++--------- api/swagger.yml | 46 +- controller/merge_request_ctl.go | 174 ++-- controller/wip_ctl.go | 2 +- integrationtest/branch_test.go | 2 +- integrationtest/commit_test.go | 4 +- integrationtest/helper_test.go | 19 +- integrationtest/merge_request_test.go | 373 ++++++++- integrationtest/objects_test.go | 2 +- integrationtest/repo_test.go | 2 +- integrationtest/wip_object_test.go | 4 +- integrationtest/wip_test.go | 4 +- models/merge_request.go | 152 +++- models/merge_request_test.go | 21 +- utils/convert_types.go | 8 + utils/convert_types_test.go | 15 + versionmgr/changes.go | 7 + versionmgr/work_repo.go | 1 + versionmgr/work_repo_test.go | 181 ++++ 19 files changed, 1587 insertions(+), 531 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 59a16d77..c0939a1e 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -100,6 +100,14 @@ type Change struct { // ChangeAction defines model for Change.Action. type ChangeAction int +// ChangePair defines model for ChangePair. +type ChangePair struct { + IsConflict bool `json:"is_conflict"` + Left *Change `json:"left,omitempty"` + Path string `json:"path"` + Right *Change `json:"right,omitempty"` +} + // Commit defines model for Commit. type Commit struct { Author Signature `json:"author"` @@ -170,13 +178,38 @@ type LoginConfig struct { // with an external auth service. type LoginConfigRBAC string +// MergeMergeRequest defines model for MergeMergeRequest. +type MergeMergeRequest struct { + // ConflictResolve use to record the resolution of the conflict, example({"b/a.txt":"base"}) + ConflictResolve *map[string]string `json:"conflict_resolve,omitempty"` + Msg string `json:"msg"` +} + // MergeRequest defines model for MergeRequest. type MergeRequest struct { AuthorId openapi_types.UUID `json:"author_id"` CreatedAt time.Time `json:"created_at"` Description *string `json:"description,omitempty"` - Id uint64 `json:"id"` + Id openapi_types.UUID `json:"id"` MergeStatus int `json:"merge_status"` + Sequence uint64 `json:"sequence"` + SourceBranch openapi_types.UUID `json:"source_branch"` + SourceRepoId openapi_types.UUID `json:"source_repo_id"` + TargetBranch openapi_types.UUID `json:"target_branch"` + TargetRepoId openapi_types.UUID `json:"target_repo_id"` + Title string `json:"title"` + UpdatedAt time.Time `json:"updated_at"` +} + +// MergeRequestFullState defines model for MergeRequestFullState. +type MergeRequestFullState struct { + AuthorId openapi_types.UUID `json:"author_id"` + Changes []ChangePair `json:"changes"` + CreatedAt time.Time `json:"created_at"` + Description *string `json:"description,omitempty"` + Id openapi_types.UUID `json:"id"` + MergeStatus int `json:"merge_status"` + Sequence uint64 `json:"sequence"` SourceBranch openapi_types.UUID `json:"source_branch"` SourceRepoId openapi_types.UUID `json:"source_repo_id"` TargetBranch openapi_types.UUID `json:"target_branch"` @@ -269,6 +302,7 @@ type Signature struct { // UpdateMergeRequest defines model for UpdateMergeRequest. type UpdateMergeRequest struct { Description *string `json:"description,omitempty"` + Status *int `json:"status,omitempty"` Title *string `json:"title,omitempty"` } @@ -278,6 +312,12 @@ type UpdateRepository struct { Head *string `json:"head,omitempty"` } +// UpdateWip defines model for UpdateWip. +type UpdateWip struct { + BaseCommit *string `json:"base_commit,omitempty"` + CurrentTree *string `json:"current_tree,omitempty"` +} + // UserInfo defines model for UserInfo. type UserInfo struct { CreatedAt time.Time `json:"created_at"` @@ -344,14 +384,12 @@ type LoginJSONBody struct { // ListMergeRequestsParams defines parameters for ListMergeRequests. type ListMergeRequestsParams struct { - // Prefix return items prefixed with this value - Prefix *PaginationPrefix `form:"prefix,omitempty" json:"prefix,omitempty"` - // After return items after this value After *PaginationRepoAfter `form:"after,omitempty" json:"after,omitempty"` // Amount how many items to return Amount *PaginationAmount `form:"amount,omitempty" json:"amount,omitempty"` + State *int `form:"state,omitempty" json:"state,omitempty"` } // DeleteObjectParams defines parameters for DeleteObject. @@ -502,6 +540,12 @@ type GetWipParams struct { RefName string `form:"refName" json:"refName"` } +// UpdateWipParams defines parameters for UpdateWip. +type UpdateWipParams struct { + // RefName ref name + RefName string `form:"refName" json:"refName"` +} + // GetWipChangesParams defines parameters for GetWipChanges. type GetWipChangesParams struct { // RefName ref name @@ -532,11 +576,14 @@ type RevertWipChangesParams struct { // LoginJSONRequestBody defines body for Login for application/json ContentType. type LoginJSONRequestBody LoginJSONBody +// UpdateMergeRequestJSONRequestBody defines body for UpdateMergeRequest for application/json ContentType. +type UpdateMergeRequestJSONRequestBody = UpdateMergeRequest + // CreateMergeRequestJSONRequestBody defines body for CreateMergeRequest for application/json ContentType. type CreateMergeRequestJSONRequestBody = CreateMergeRequest -// UpdateMergeRequestJSONRequestBody defines body for UpdateMergeRequest for application/json ContentType. -type UpdateMergeRequestJSONRequestBody = UpdateMergeRequest +// MergeJSONRequestBody defines body for Merge for application/json ContentType. +type MergeJSONRequestBody = MergeMergeRequest // UploadObjectMultipartRequestBody defines body for UploadObject for multipart/form-data ContentType. type UploadObjectMultipartRequestBody UploadObjectMultipartBody @@ -553,6 +600,9 @@ type RegisterJSONRequestBody = UserRegisterInfo // CreateRepositoryJSONRequestBody defines body for CreateRepository for application/json ContentType. type CreateRepositoryJSONRequestBody = CreateRepository +// UpdateWipJSONRequestBody defines body for UpdateWip for application/json ContentType. +type UpdateWipJSONRequestBody = UpdateWip + // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error @@ -634,6 +684,14 @@ type ClientInterface interface { // Logout request Logout(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetMergeRequest request + GetMergeRequest(ctx context.Context, owner string, repository string, mrSeq uint64, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UpdateMergeRequestWithBody request with any body + UpdateMergeRequestWithBody(ctx context.Context, owner string, repository string, mrSeq uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + UpdateMergeRequest(ctx context.Context, owner string, repository string, mrSeq uint64, body UpdateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // ListMergeRequests request ListMergeRequests(ctx context.Context, owner string, repository string, params *ListMergeRequestsParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -642,16 +700,10 @@ type ClientInterface interface { CreateMergeRequest(ctx context.Context, owner string, repository string, body CreateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // DeleteMergeRequest request - DeleteMergeRequest(ctx context.Context, owner string, repository string, mrId uint64, reqEditors ...RequestEditorFn) (*http.Response, error) - - // GetMergeRequest request - GetMergeRequest(ctx context.Context, owner string, repository string, mrId uint64, reqEditors ...RequestEditorFn) (*http.Response, error) - - // UpdateMergeRequestWithBody request with any body - UpdateMergeRequestWithBody(ctx context.Context, owner string, repository string, mrId uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + // MergeWithBody request with any body + MergeWithBody(ctx context.Context, owner string, repository string, mrSeq uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - UpdateMergeRequest(ctx context.Context, owner string, repository string, mrId uint64, body UpdateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + Merge(ctx context.Context, owner string, repository string, mrSeq uint64, body MergeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // DeleteObject request DeleteObject(ctx context.Context, owner string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -733,6 +785,11 @@ type ClientInterface interface { // GetWip request GetWip(ctx context.Context, owner string, repository string, params *GetWipParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // UpdateWipWithBody request with any body + UpdateWipWithBody(ctx context.Context, owner string, repository string, params *UpdateWipParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + UpdateWip(ctx context.Context, owner string, repository string, params *UpdateWipParams, body UpdateWipJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetWipChanges request GetWipChanges(ctx context.Context, owner string, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -782,8 +839,8 @@ func (c *Client) Logout(ctx context.Context, reqEditors ...RequestEditorFn) (*ht return c.Client.Do(req) } -func (c *Client) ListMergeRequests(ctx context.Context, owner string, repository string, params *ListMergeRequestsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewListMergeRequestsRequest(c.Server, owner, repository, params) +func (c *Client) GetMergeRequest(ctx context.Context, owner string, repository string, mrSeq uint64, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetMergeRequestRequest(c.Server, owner, repository, mrSeq) if err != nil { return nil, err } @@ -794,8 +851,8 @@ func (c *Client) ListMergeRequests(ctx context.Context, owner string, repository return c.Client.Do(req) } -func (c *Client) CreateMergeRequestWithBody(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateMergeRequestRequestWithBody(c.Server, owner, repository, contentType, body) +func (c *Client) UpdateMergeRequestWithBody(ctx context.Context, owner string, repository string, mrSeq uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateMergeRequestRequestWithBody(c.Server, owner, repository, mrSeq, contentType, body) if err != nil { return nil, err } @@ -806,8 +863,20 @@ func (c *Client) CreateMergeRequestWithBody(ctx context.Context, owner string, r return c.Client.Do(req) } -func (c *Client) CreateMergeRequest(ctx context.Context, owner string, repository string, body CreateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateMergeRequestRequest(c.Server, owner, repository, body) +func (c *Client) UpdateMergeRequest(ctx context.Context, owner string, repository string, mrSeq uint64, body UpdateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateMergeRequestRequest(c.Server, owner, repository, mrSeq, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) ListMergeRequests(ctx context.Context, owner string, repository string, params *ListMergeRequestsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListMergeRequestsRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -818,8 +887,8 @@ func (c *Client) CreateMergeRequest(ctx context.Context, owner string, repositor return c.Client.Do(req) } -func (c *Client) DeleteMergeRequest(ctx context.Context, owner string, repository string, mrId uint64, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteMergeRequestRequest(c.Server, owner, repository, mrId) +func (c *Client) CreateMergeRequestWithBody(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateMergeRequestRequestWithBody(c.Server, owner, repository, contentType, body) if err != nil { return nil, err } @@ -830,8 +899,8 @@ func (c *Client) DeleteMergeRequest(ctx context.Context, owner string, repositor return c.Client.Do(req) } -func (c *Client) GetMergeRequest(ctx context.Context, owner string, repository string, mrId uint64, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetMergeRequestRequest(c.Server, owner, repository, mrId) +func (c *Client) CreateMergeRequest(ctx context.Context, owner string, repository string, body CreateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateMergeRequestRequest(c.Server, owner, repository, body) if err != nil { return nil, err } @@ -842,8 +911,8 @@ func (c *Client) GetMergeRequest(ctx context.Context, owner string, repository s return c.Client.Do(req) } -func (c *Client) UpdateMergeRequestWithBody(ctx context.Context, owner string, repository string, mrId uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewUpdateMergeRequestRequestWithBody(c.Server, owner, repository, mrId, contentType, body) +func (c *Client) MergeWithBody(ctx context.Context, owner string, repository string, mrSeq uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewMergeRequestWithBody(c.Server, owner, repository, mrSeq, contentType, body) if err != nil { return nil, err } @@ -854,8 +923,8 @@ func (c *Client) UpdateMergeRequestWithBody(ctx context.Context, owner string, r return c.Client.Do(req) } -func (c *Client) UpdateMergeRequest(ctx context.Context, owner string, repository string, mrId uint64, body UpdateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewUpdateMergeRequestRequest(c.Server, owner, repository, mrId, body) +func (c *Client) Merge(ctx context.Context, owner string, repository string, mrSeq uint64, body MergeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewMergeRequest(c.Server, owner, repository, mrSeq, body) if err != nil { return nil, err } @@ -1202,6 +1271,30 @@ func (c *Client) GetWip(ctx context.Context, owner string, repository string, pa return c.Client.Do(req) } +func (c *Client) UpdateWipWithBody(ctx context.Context, owner string, repository string, params *UpdateWipParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateWipRequestWithBody(c.Server, owner, repository, params, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateWip(ctx context.Context, owner string, repository string, params *UpdateWipParams, body UpdateWipJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateWipRequest(c.Server, owner, repository, params, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) GetWipChanges(ctx context.Context, owner string, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewGetWipChangesRequest(c.Server, owner, repository, params) if err != nil { @@ -1317,8 +1410,8 @@ func NewLogoutRequest(server string) (*http.Request, error) { return req, nil } -// NewListMergeRequestsRequest generates requests for ListMergeRequests -func NewListMergeRequestsRequest(server string, owner string, repository string, params *ListMergeRequestsParams) (*http.Request, error) { +// NewGetMergeRequestRequest generates requests for GetMergeRequest +func NewGetMergeRequestRequest(server string, owner string, repository string, mrSeq uint64) (*http.Request, error) { var err error var pathParam0 string @@ -1335,12 +1428,19 @@ func NewListMergeRequestsRequest(server string, owner string, repository string, return nil, err } + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "mrSeq", runtime.ParamLocationPath, mrSeq) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/mergequest/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/mergequest/%s/%s/%s", pathParam0, pathParam1, pathParam2) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1350,60 +1450,6 @@ func NewListMergeRequestsRequest(server string, owner string, repository string, return nil, err } - if params != nil { - queryValues := queryURL.Query() - - if params.Prefix != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "prefix", runtime.ParamLocationQuery, *params.Prefix); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - if params.After != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "after", runtime.ParamLocationQuery, *params.After); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - if params.Amount != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "amount", runtime.ParamLocationQuery, *params.Amount); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - queryURL.RawQuery = queryValues.Encode() - } - req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err @@ -1412,19 +1458,19 @@ func NewListMergeRequestsRequest(server string, owner string, repository string, return req, nil } -// NewCreateMergeRequestRequest calls the generic CreateMergeRequest builder with application/json body -func NewCreateMergeRequestRequest(server string, owner string, repository string, body CreateMergeRequestJSONRequestBody) (*http.Request, error) { +// NewUpdateMergeRequestRequest calls the generic UpdateMergeRequest builder with application/json body +func NewUpdateMergeRequestRequest(server string, owner string, repository string, mrSeq uint64, body UpdateMergeRequestJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewCreateMergeRequestRequestWithBody(server, owner, repository, "application/json", bodyReader) + return NewUpdateMergeRequestRequestWithBody(server, owner, repository, mrSeq, "application/json", bodyReader) } -// NewCreateMergeRequestRequestWithBody generates requests for CreateMergeRequest with any type of body -func NewCreateMergeRequestRequestWithBody(server string, owner string, repository string, contentType string, body io.Reader) (*http.Request, error) { +// NewUpdateMergeRequestRequestWithBody generates requests for UpdateMergeRequest with any type of body +func NewUpdateMergeRequestRequestWithBody(server string, owner string, repository string, mrSeq uint64, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -1441,12 +1487,19 @@ func NewCreateMergeRequestRequestWithBody(server string, owner string, repositor return nil, err } + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "mrSeq", runtime.ParamLocationPath, mrSeq) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/mergequest/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/mergequest/%s/%s/%s", pathParam0, pathParam1, pathParam2) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1466,8 +1519,8 @@ func NewCreateMergeRequestRequestWithBody(server string, owner string, repositor return req, nil } -// NewDeleteMergeRequestRequest generates requests for DeleteMergeRequest -func NewDeleteMergeRequestRequest(server string, owner string, repository string, mrId uint64) (*http.Request, error) { +// NewListMergeRequestsRequest generates requests for ListMergeRequests +func NewListMergeRequestsRequest(server string, owner string, repository string, params *ListMergeRequestsParams) (*http.Request, error) { var err error var pathParam0 string @@ -1484,19 +1537,12 @@ func NewDeleteMergeRequestRequest(server string, owner string, repository string return nil, err } - var pathParam2 string - - pathParam2, err = runtime.StyleParamWithLocation("simple", false, "mrId", runtime.ParamLocationPath, mrId) - if err != nil { - return nil, err - } - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/mergequest/%s/%s/mr/%s", pathParam0, pathParam1, pathParam2) + operationPath := fmt.Sprintf("/mergerequest/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1506,7 +1552,61 @@ func NewDeleteMergeRequestRequest(server string, owner string, repository string return nil, err } - req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if params != nil { + queryValues := queryURL.Query() + + if params.After != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "after", runtime.ParamLocationQuery, *params.After); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Amount != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "amount", runtime.ParamLocationQuery, *params.Amount); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.State != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "state", runtime.ParamLocationQuery, *params.State); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } @@ -1514,8 +1614,19 @@ func NewDeleteMergeRequestRequest(server string, owner string, repository string return req, nil } -// NewGetMergeRequestRequest generates requests for GetMergeRequest -func NewGetMergeRequestRequest(server string, owner string, repository string, mrId uint64) (*http.Request, error) { +// NewCreateMergeRequestRequest calls the generic CreateMergeRequest builder with application/json body +func NewCreateMergeRequestRequest(server string, owner string, repository string, body CreateMergeRequestJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateMergeRequestRequestWithBody(server, owner, repository, "application/json", bodyReader) +} + +// NewCreateMergeRequestRequestWithBody generates requests for CreateMergeRequest with any type of body +func NewCreateMergeRequestRequestWithBody(server string, owner string, repository string, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -1532,19 +1643,12 @@ func NewGetMergeRequestRequest(server string, owner string, repository string, m return nil, err } - var pathParam2 string - - pathParam2, err = runtime.StyleParamWithLocation("simple", false, "mrId", runtime.ParamLocationPath, mrId) - if err != nil { - return nil, err - } - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/mergequest/%s/%s/mr/%s", pathParam0, pathParam1, pathParam2) + operationPath := fmt.Sprintf("/mergerequest/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1554,27 +1658,29 @@ func NewGetMergeRequestRequest(server string, owner string, repository string, m return nil, err } - req, err := http.NewRequest("GET", queryURL.String(), nil) + req, err := http.NewRequest("POST", queryURL.String(), body) if err != nil { return nil, err } + req.Header.Add("Content-Type", contentType) + return req, nil } -// NewUpdateMergeRequestRequest calls the generic UpdateMergeRequest builder with application/json body -func NewUpdateMergeRequestRequest(server string, owner string, repository string, mrId uint64, body UpdateMergeRequestJSONRequestBody) (*http.Request, error) { +// NewMergeRequest calls the generic Merge builder with application/json body +func NewMergeRequest(server string, owner string, repository string, mrSeq uint64, body MergeJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewUpdateMergeRequestRequestWithBody(server, owner, repository, mrId, "application/json", bodyReader) + return NewMergeRequestWithBody(server, owner, repository, mrSeq, "application/json", bodyReader) } -// NewUpdateMergeRequestRequestWithBody generates requests for UpdateMergeRequest with any type of body -func NewUpdateMergeRequestRequestWithBody(server string, owner string, repository string, mrId uint64, contentType string, body io.Reader) (*http.Request, error) { +// NewMergeRequestWithBody generates requests for Merge with any type of body +func NewMergeRequestWithBody(server string, owner string, repository string, mrSeq uint64, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -1593,7 +1699,7 @@ func NewUpdateMergeRequestRequestWithBody(server string, owner string, repositor var pathParam2 string - pathParam2, err = runtime.StyleParamWithLocation("simple", false, "mrId", runtime.ParamLocationPath, mrId) + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "mrSeq", runtime.ParamLocationPath, mrSeq) if err != nil { return nil, err } @@ -1603,7 +1709,7 @@ func NewUpdateMergeRequestRequestWithBody(server string, owner string, repositor return nil, err } - operationPath := fmt.Sprintf("/mergequest/%s/%s/mr/%s", pathParam0, pathParam1, pathParam2) + operationPath := fmt.Sprintf("/mergerequest/%s/%s/%s/merge", pathParam0, pathParam1, pathParam2) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -3140,6 +3246,78 @@ func NewGetWipRequest(server string, owner string, repository string, params *Ge return req, nil } +// NewUpdateWipRequest calls the generic UpdateWip builder with application/json body +func NewUpdateWipRequest(server string, owner string, repository string, params *UpdateWipParams, body UpdateWipJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewUpdateWipRequestWithBody(server, owner, repository, params, "application/json", bodyReader) +} + +// NewUpdateWipRequestWithBody generates requests for UpdateWip with any type of body +func NewUpdateWipRequestWithBody(server string, owner string, repository string, params *UpdateWipParams, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/wip/%s/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewGetWipChangesRequest generates requests for GetWipChanges func NewGetWipChangesRequest(server string, owner string, repository string, params *GetWipChangesParams) (*http.Request, error) { var err error @@ -3453,6 +3631,14 @@ type ClientWithResponsesInterface interface { // LogoutWithResponse request LogoutWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*LogoutResponse, error) + // GetMergeRequestWithResponse request + GetMergeRequestWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, reqEditors ...RequestEditorFn) (*GetMergeRequestResponse, error) + + // UpdateMergeRequestWithBodyWithResponse request with any body + UpdateMergeRequestWithBodyWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateMergeRequestResponse, error) + + UpdateMergeRequestWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, body UpdateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateMergeRequestResponse, error) + // ListMergeRequestsWithResponse request ListMergeRequestsWithResponse(ctx context.Context, owner string, repository string, params *ListMergeRequestsParams, reqEditors ...RequestEditorFn) (*ListMergeRequestsResponse, error) @@ -3461,16 +3647,10 @@ type ClientWithResponsesInterface interface { CreateMergeRequestWithResponse(ctx context.Context, owner string, repository string, body CreateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateMergeRequestResponse, error) - // DeleteMergeRequestWithResponse request - DeleteMergeRequestWithResponse(ctx context.Context, owner string, repository string, mrId uint64, reqEditors ...RequestEditorFn) (*DeleteMergeRequestResponse, error) - - // GetMergeRequestWithResponse request - GetMergeRequestWithResponse(ctx context.Context, owner string, repository string, mrId uint64, reqEditors ...RequestEditorFn) (*GetMergeRequestResponse, error) - - // UpdateMergeRequestWithBodyWithResponse request with any body - UpdateMergeRequestWithBodyWithResponse(ctx context.Context, owner string, repository string, mrId uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateMergeRequestResponse, error) + // MergeWithBodyWithResponse request with any body + MergeWithBodyWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MergeResponse, error) - UpdateMergeRequestWithResponse(ctx context.Context, owner string, repository string, mrId uint64, body UpdateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateMergeRequestResponse, error) + MergeWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, body MergeJSONRequestBody, reqEditors ...RequestEditorFn) (*MergeResponse, error) // DeleteObjectWithResponse request DeleteObjectWithResponse(ctx context.Context, owner string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) @@ -3552,6 +3732,11 @@ type ClientWithResponsesInterface interface { // GetWipWithResponse request GetWipWithResponse(ctx context.Context, owner string, repository string, params *GetWipParams, reqEditors ...RequestEditorFn) (*GetWipResponse, error) + // UpdateWipWithBodyWithResponse request with any body + UpdateWipWithBodyWithResponse(ctx context.Context, owner string, repository string, params *UpdateWipParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateWipResponse, error) + + UpdateWipWithResponse(ctx context.Context, owner string, repository string, params *UpdateWipParams, body UpdateWipJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateWipResponse, error) + // GetWipChangesWithResponse request GetWipChangesWithResponse(ctx context.Context, owner string, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*GetWipChangesResponse, error) @@ -3608,14 +3793,14 @@ func (r LogoutResponse) StatusCode() int { return 0 } -type ListMergeRequestsResponse struct { +type GetMergeRequestResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *MergeRequestList + JSON200 *MergeRequestFullState } // Status returns HTTPResponse.Status -func (r ListMergeRequestsResponse) Status() string { +func (r GetMergeRequestResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3623,21 +3808,20 @@ func (r ListMergeRequestsResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r ListMergeRequestsResponse) StatusCode() int { +func (r GetMergeRequestResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type CreateMergeRequestResponse struct { +type UpdateMergeRequestResponse struct { Body []byte HTTPResponse *http.Response - JSON201 *MergeRequest } // Status returns HTTPResponse.Status -func (r CreateMergeRequestResponse) Status() string { +func (r UpdateMergeRequestResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3645,20 +3829,21 @@ func (r CreateMergeRequestResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r CreateMergeRequestResponse) StatusCode() int { +func (r UpdateMergeRequestResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type DeleteMergeRequestResponse struct { +type ListMergeRequestsResponse struct { Body []byte HTTPResponse *http.Response + JSON200 *MergeRequestList } // Status returns HTTPResponse.Status -func (r DeleteMergeRequestResponse) Status() string { +func (r ListMergeRequestsResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3666,21 +3851,21 @@ func (r DeleteMergeRequestResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r DeleteMergeRequestResponse) StatusCode() int { +func (r ListMergeRequestsResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetMergeRequestResponse struct { +type CreateMergeRequestResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *MergeRequest + JSON201 *MergeRequest } // Status returns HTTPResponse.Status -func (r GetMergeRequestResponse) Status() string { +func (r CreateMergeRequestResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3688,20 +3873,21 @@ func (r GetMergeRequestResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetMergeRequestResponse) StatusCode() int { +func (r CreateMergeRequestResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type UpdateMergeRequestResponse struct { +type MergeResponse struct { Body []byte HTTPResponse *http.Response + JSON200 *[]Commit } // Status returns HTTPResponse.Status -func (r UpdateMergeRequestResponse) Status() string { +func (r MergeResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3709,7 +3895,7 @@ func (r UpdateMergeRequestResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r UpdateMergeRequestResponse) StatusCode() int { +func (r MergeResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -4236,6 +4422,27 @@ func (r GetWipResponse) StatusCode() int { return 0 } +type UpdateWipResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r UpdateWipResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UpdateWipResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type GetWipChangesResponse struct { Body []byte HTTPResponse *http.Response @@ -4349,6 +4556,32 @@ func (c *ClientWithResponses) LogoutWithResponse(ctx context.Context, reqEditors return ParseLogoutResponse(rsp) } +// GetMergeRequestWithResponse request returning *GetMergeRequestResponse +func (c *ClientWithResponses) GetMergeRequestWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, reqEditors ...RequestEditorFn) (*GetMergeRequestResponse, error) { + rsp, err := c.GetMergeRequest(ctx, owner, repository, mrSeq, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetMergeRequestResponse(rsp) +} + +// UpdateMergeRequestWithBodyWithResponse request with arbitrary body returning *UpdateMergeRequestResponse +func (c *ClientWithResponses) UpdateMergeRequestWithBodyWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateMergeRequestResponse, error) { + rsp, err := c.UpdateMergeRequestWithBody(ctx, owner, repository, mrSeq, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateMergeRequestResponse(rsp) +} + +func (c *ClientWithResponses) UpdateMergeRequestWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, body UpdateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateMergeRequestResponse, error) { + rsp, err := c.UpdateMergeRequest(ctx, owner, repository, mrSeq, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateMergeRequestResponse(rsp) +} + // ListMergeRequestsWithResponse request returning *ListMergeRequestsResponse func (c *ClientWithResponses) ListMergeRequestsWithResponse(ctx context.Context, owner string, repository string, params *ListMergeRequestsParams, reqEditors ...RequestEditorFn) (*ListMergeRequestsResponse, error) { rsp, err := c.ListMergeRequests(ctx, owner, repository, params, reqEditors...) @@ -4375,39 +4608,21 @@ func (c *ClientWithResponses) CreateMergeRequestWithResponse(ctx context.Context return ParseCreateMergeRequestResponse(rsp) } -// DeleteMergeRequestWithResponse request returning *DeleteMergeRequestResponse -func (c *ClientWithResponses) DeleteMergeRequestWithResponse(ctx context.Context, owner string, repository string, mrId uint64, reqEditors ...RequestEditorFn) (*DeleteMergeRequestResponse, error) { - rsp, err := c.DeleteMergeRequest(ctx, owner, repository, mrId, reqEditors...) +// MergeWithBodyWithResponse request with arbitrary body returning *MergeResponse +func (c *ClientWithResponses) MergeWithBodyWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MergeResponse, error) { + rsp, err := c.MergeWithBody(ctx, owner, repository, mrSeq, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseDeleteMergeRequestResponse(rsp) + return ParseMergeResponse(rsp) } -// GetMergeRequestWithResponse request returning *GetMergeRequestResponse -func (c *ClientWithResponses) GetMergeRequestWithResponse(ctx context.Context, owner string, repository string, mrId uint64, reqEditors ...RequestEditorFn) (*GetMergeRequestResponse, error) { - rsp, err := c.GetMergeRequest(ctx, owner, repository, mrId, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetMergeRequestResponse(rsp) -} - -// UpdateMergeRequestWithBodyWithResponse request with arbitrary body returning *UpdateMergeRequestResponse -func (c *ClientWithResponses) UpdateMergeRequestWithBodyWithResponse(ctx context.Context, owner string, repository string, mrId uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateMergeRequestResponse, error) { - rsp, err := c.UpdateMergeRequestWithBody(ctx, owner, repository, mrId, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseUpdateMergeRequestResponse(rsp) -} - -func (c *ClientWithResponses) UpdateMergeRequestWithResponse(ctx context.Context, owner string, repository string, mrId uint64, body UpdateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateMergeRequestResponse, error) { - rsp, err := c.UpdateMergeRequest(ctx, owner, repository, mrId, body, reqEditors...) +func (c *ClientWithResponses) MergeWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, body MergeJSONRequestBody, reqEditors ...RequestEditorFn) (*MergeResponse, error) { + rsp, err := c.Merge(ctx, owner, repository, mrSeq, body, reqEditors...) if err != nil { return nil, err } - return ParseUpdateMergeRequestResponse(rsp) + return ParseMergeResponse(rsp) } // DeleteObjectWithResponse request returning *DeleteObjectResponse @@ -4655,7 +4870,24 @@ func (c *ClientWithResponses) GetWipWithResponse(ctx context.Context, owner stri if err != nil { return nil, err } - return ParseGetWipResponse(rsp) + return ParseGetWipResponse(rsp) +} + +// UpdateWipWithBodyWithResponse request with arbitrary body returning *UpdateWipResponse +func (c *ClientWithResponses) UpdateWipWithBodyWithResponse(ctx context.Context, owner string, repository string, params *UpdateWipParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateWipResponse, error) { + rsp, err := c.UpdateWipWithBody(ctx, owner, repository, params, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateWipResponse(rsp) +} + +func (c *ClientWithResponses) UpdateWipWithResponse(ctx context.Context, owner string, repository string, params *UpdateWipParams, body UpdateWipJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateWipResponse, error) { + rsp, err := c.UpdateWip(ctx, owner, repository, params, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateWipResponse(rsp) } // GetWipChangesWithResponse request returning *GetWipChangesResponse @@ -4736,22 +4968,22 @@ func ParseLogoutResponse(rsp *http.Response) (*LogoutResponse, error) { return response, nil } -// ParseListMergeRequestsResponse parses an HTTP response from a ListMergeRequestsWithResponse call -func ParseListMergeRequestsResponse(rsp *http.Response) (*ListMergeRequestsResponse, error) { +// ParseGetMergeRequestResponse parses an HTTP response from a GetMergeRequestWithResponse call +func ParseGetMergeRequestResponse(rsp *http.Response) (*GetMergeRequestResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &ListMergeRequestsResponse{ + response := &GetMergeRequestResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest MergeRequestList + var dest MergeRequestFullState if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -4762,87 +4994,97 @@ func ParseListMergeRequestsResponse(rsp *http.Response) (*ListMergeRequestsRespo return response, nil } -// ParseCreateMergeRequestResponse parses an HTTP response from a CreateMergeRequestWithResponse call -func ParseCreateMergeRequestResponse(rsp *http.Response) (*CreateMergeRequestResponse, error) { +// ParseUpdateMergeRequestResponse parses an HTTP response from a UpdateMergeRequestWithResponse call +func ParseUpdateMergeRequestResponse(rsp *http.Response) (*UpdateMergeRequestResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CreateMergeRequestResponse{ + response := &UpdateMergeRequestResponse{ Body: bodyBytes, HTTPResponse: rsp, } - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest MergeRequest - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON201 = &dest - - } - return response, nil } -// ParseDeleteMergeRequestResponse parses an HTTP response from a DeleteMergeRequestWithResponse call -func ParseDeleteMergeRequestResponse(rsp *http.Response) (*DeleteMergeRequestResponse, error) { +// ParseListMergeRequestsResponse parses an HTTP response from a ListMergeRequestsWithResponse call +func ParseListMergeRequestsResponse(rsp *http.Response) (*ListMergeRequestsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &DeleteMergeRequestResponse{ + response := &ListMergeRequestsResponse{ Body: bodyBytes, HTTPResponse: rsp, } + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest MergeRequestList + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + return response, nil } -// ParseGetMergeRequestResponse parses an HTTP response from a GetMergeRequestWithResponse call -func ParseGetMergeRequestResponse(rsp *http.Response) (*GetMergeRequestResponse, error) { +// ParseCreateMergeRequestResponse parses an HTTP response from a CreateMergeRequestWithResponse call +func ParseCreateMergeRequestResponse(rsp *http.Response) (*CreateMergeRequestResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetMergeRequestResponse{ + response := &CreateMergeRequestResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: var dest MergeRequest if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON200 = &dest + response.JSON201 = &dest } return response, nil } -// ParseUpdateMergeRequestResponse parses an HTTP response from a UpdateMergeRequestWithResponse call -func ParseUpdateMergeRequestResponse(rsp *http.Response) (*UpdateMergeRequestResponse, error) { +// ParseMergeResponse parses an HTTP response from a MergeWithResponse call +func ParseMergeResponse(rsp *http.Response) (*MergeResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &UpdateMergeRequestResponse{ + response := &MergeResponse{ Body: bodyBytes, HTTPResponse: rsp, } + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []Commit + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + return response, nil } @@ -5390,6 +5632,22 @@ func ParseGetWipResponse(rsp *http.Response) (*GetWipResponse, error) { return response, nil } +// ParseUpdateWipResponse parses an HTTP response from a UpdateWipWithResponse call +func ParseUpdateWipResponse(rsp *http.Response) (*UpdateWipResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UpdateWipResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + // ParseGetWipChangesResponse parses an HTTP response from a GetWipChangesWithResponse call func ParseGetWipChangesResponse(rsp *http.Response) (*GetWipChangesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -5492,21 +5750,21 @@ type ServerInterface interface { // perform a logout // (POST /auth/logout) Logout(ctx context.Context, w *JiaozifsResponse, r *http.Request) + // get merge request + // (GET /mergequest/{owner}/{repository}/{mrSeq}) + GetMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, mrSeq uint64) + // update merge request + // (POST /mergequest/{owner}/{repository}/{mrSeq}) + UpdateMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateMergeRequestJSONRequestBody, owner string, repository string, mrSeq uint64) // get list of merge request in repository - // (GET /mergequest/{owner}/{repository}) + // (GET /mergerequest/{owner}/{repository}) ListMergeRequests(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params ListMergeRequestsParams) // create merge request - // (POST /mergequest/{owner}/{repository}) + // (POST /mergerequest/{owner}/{repository}) CreateMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateMergeRequestJSONRequestBody, owner string, repository string) - // delete merge request - // (DELETE /mergequest/{owner}/{repository}/mr/{mrId}) - DeleteMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, mrId uint64) - // get merge request - // (GET /mergequest/{owner}/{repository}/mr/{mrId}) - GetMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, mrId uint64) - // update merge request - // (POST /mergequest/{owner}/{repository}/mr/{mrId}) - UpdateMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateMergeRequestJSONRequestBody, owner string, repository string, mrId uint64) + // merge a mergerequest + // (POST /mergerequest/{owner}/{repository}/{mrSeq}/merge) + Merge(ctx context.Context, w *JiaozifsResponse, r *http.Request, body MergeJSONRequestBody, owner string, repository string, mrSeq uint64) // delete object. Missing objects will not return a NotFound error. // (DELETE /object/{owner}/{repository}) DeleteObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteObjectParams) @@ -5579,6 +5837,9 @@ type ServerInterface interface { // get working in process // (GET /wip/{owner}/{repository}) GetWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetWipParams) + // update wip + // (POST /wip/{owner}/{repository}) + UpdateWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateWipJSONRequestBody, owner string, repository string, params UpdateWipParams) // get working in process changes // (GET /wip/{owner}/{repository}/changes) GetWipChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetWipChangesParams) @@ -5609,33 +5870,33 @@ func (_ Unimplemented) Logout(ctx context.Context, w *JiaozifsResponse, r *http. w.WriteHeader(http.StatusNotImplemented) } -// get list of merge request in repository -// (GET /mergequest/{owner}/{repository}) -func (_ Unimplemented) ListMergeRequests(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params ListMergeRequestsParams) { +// get merge request +// (GET /mergequest/{owner}/{repository}/{mrSeq}) +func (_ Unimplemented) GetMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, mrSeq uint64) { w.WriteHeader(http.StatusNotImplemented) } -// create merge request -// (POST /mergequest/{owner}/{repository}) -func (_ Unimplemented) CreateMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateMergeRequestJSONRequestBody, owner string, repository string) { +// update merge request +// (POST /mergequest/{owner}/{repository}/{mrSeq}) +func (_ Unimplemented) UpdateMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateMergeRequestJSONRequestBody, owner string, repository string, mrSeq uint64) { w.WriteHeader(http.StatusNotImplemented) } -// delete merge request -// (DELETE /mergequest/{owner}/{repository}/mr/{mrId}) -func (_ Unimplemented) DeleteMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, mrId uint64) { +// get list of merge request in repository +// (GET /mergerequest/{owner}/{repository}) +func (_ Unimplemented) ListMergeRequests(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params ListMergeRequestsParams) { w.WriteHeader(http.StatusNotImplemented) } -// get merge request -// (GET /mergequest/{owner}/{repository}/mr/{mrId}) -func (_ Unimplemented) GetMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, mrId uint64) { +// create merge request +// (POST /mergerequest/{owner}/{repository}) +func (_ Unimplemented) CreateMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateMergeRequestJSONRequestBody, owner string, repository string) { w.WriteHeader(http.StatusNotImplemented) } -// update merge request -// (POST /mergequest/{owner}/{repository}/mr/{mrId}) -func (_ Unimplemented) UpdateMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateMergeRequestJSONRequestBody, owner string, repository string, mrId uint64) { +// merge a mergerequest +// (POST /mergerequest/{owner}/{repository}/{mrSeq}/merge) +func (_ Unimplemented) Merge(ctx context.Context, w *JiaozifsResponse, r *http.Request, body MergeJSONRequestBody, owner string, repository string, mrSeq uint64) { w.WriteHeader(http.StatusNotImplemented) } @@ -5782,6 +6043,12 @@ func (_ Unimplemented) GetWip(ctx context.Context, w *JiaozifsResponse, r *http. w.WriteHeader(http.StatusNotImplemented) } +// update wip +// (POST /wip/{owner}/{repository}) +func (_ Unimplemented) UpdateWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateWipJSONRequestBody, owner string, repository string, params UpdateWipParams) { + w.WriteHeader(http.StatusNotImplemented) +} + // get working in process changes // (GET /wip/{owner}/{repository}/changes) func (_ Unimplemented) GetWipChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetWipChangesParams) { @@ -5861,8 +6128,8 @@ func (siw *ServerInterfaceWrapper) Logout(w http.ResponseWriter, r *http.Request handler.ServeHTTP(w, r.WithContext(ctx)) } -// ListMergeRequests operation middleware -func (siw *ServerInterfaceWrapper) ListMergeRequests(w http.ResponseWriter, r *http.Request) { +// GetMergeRequest operation middleware +func (siw *ServerInterfaceWrapper) GetMergeRequest(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -5885,41 +6152,23 @@ func (siw *ServerInterfaceWrapper) ListMergeRequests(w http.ResponseWriter, r *h return } - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - - ctx = context.WithValue(ctx, Basic_authScopes, []string{}) - - ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - - // Parameter object where we will unmarshal all parameters from the context - var params ListMergeRequestsParams + // ------------- Path parameter "mrSeq" ------------- + var mrSeq uint64 - // ------------- Optional query parameter "prefix" ------------- - - err = runtime.BindQueryParameter("form", true, false, "prefix", r.URL.Query(), ¶ms.Prefix) + err = runtime.BindStyledParameterWithOptions("simple", "mrSeq", chi.URLParam(r, "mrSeq"), &mrSeq, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "prefix", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "mrSeq", Err: err}) return } - // ------------- Optional query parameter "after" ------------- - - err = runtime.BindQueryParameter("form", true, false, "after", r.URL.Query(), ¶ms.After) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "after", Err: err}) - return - } + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - // ------------- Optional query parameter "amount" ------------- + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) - err = runtime.BindQueryParameter("form", true, false, "amount", r.URL.Query(), ¶ms.Amount) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "amount", Err: err}) - return - } + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.ListMergeRequests(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + siw.Handler.GetMergeRequest(r.Context(), &JiaozifsResponse{w}, r, owner, repository, mrSeq) })) for _, middleware := range siw.HandlerMiddlewares { @@ -5929,18 +6178,18 @@ func (siw *ServerInterfaceWrapper) ListMergeRequests(w http.ResponseWriter, r *h handler.ServeHTTP(w, r.WithContext(ctx)) } -// CreateMergeRequest operation middleware -func (siw *ServerInterfaceWrapper) CreateMergeRequest(w http.ResponseWriter, r *http.Request) { +// UpdateMergeRequest operation middleware +func (siw *ServerInterfaceWrapper) UpdateMergeRequest(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error // ------------- Body parse ------------- - var body CreateMergeRequestJSONRequestBody + var body UpdateMergeRequestJSONRequestBody parseBody := true if parseBody { if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - http.Error(w, "Error unmarshalling body 'CreateMergeRequest' as JSON", http.StatusBadRequest) + http.Error(w, "Error unmarshalling body 'UpdateMergeRequest' as JSON", http.StatusBadRequest) return } } @@ -5963,6 +6212,15 @@ func (siw *ServerInterfaceWrapper) CreateMergeRequest(w http.ResponseWriter, r * return } + // ------------- Path parameter "mrSeq" ------------- + var mrSeq uint64 + + err = runtime.BindStyledParameterWithOptions("simple", "mrSeq", chi.URLParam(r, "mrSeq"), &mrSeq, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "mrSeq", Err: err}) + return + } + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) ctx = context.WithValue(ctx, Basic_authScopes, []string{}) @@ -5970,7 +6228,7 @@ func (siw *ServerInterfaceWrapper) CreateMergeRequest(w http.ResponseWriter, r * ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CreateMergeRequest(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository) + siw.Handler.UpdateMergeRequest(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository, mrSeq) })) for _, middleware := range siw.HandlerMiddlewares { @@ -5980,8 +6238,8 @@ func (siw *ServerInterfaceWrapper) CreateMergeRequest(w http.ResponseWriter, r * handler.ServeHTTP(w, r.WithContext(ctx)) } -// DeleteMergeRequest operation middleware -func (siw *ServerInterfaceWrapper) DeleteMergeRequest(w http.ResponseWriter, r *http.Request) { +// ListMergeRequests operation middleware +func (siw *ServerInterfaceWrapper) ListMergeRequests(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -6004,23 +6262,41 @@ func (siw *ServerInterfaceWrapper) DeleteMergeRequest(w http.ResponseWriter, r * return } - // ------------- Path parameter "mrId" ------------- - var mrId uint64 + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params ListMergeRequestsParams + + // ------------- Optional query parameter "after" ------------- - err = runtime.BindStyledParameterWithOptions("simple", "mrId", chi.URLParam(r, "mrId"), &mrId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindQueryParameter("form", true, false, "after", r.URL.Query(), ¶ms.After) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "mrId", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "after", Err: err}) return } - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + // ------------- Optional query parameter "amount" ------------- - ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + err = runtime.BindQueryParameter("form", true, false, "amount", r.URL.Query(), ¶ms.Amount) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "amount", Err: err}) + return + } - ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + // ------------- Optional query parameter "state" ------------- + + err = runtime.BindQueryParameter("form", true, false, "state", r.URL.Query(), ¶ms.State) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "state", Err: err}) + return + } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.DeleteMergeRequest(r.Context(), &JiaozifsResponse{w}, r, owner, repository, mrId) + siw.Handler.ListMergeRequests(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -6030,12 +6306,22 @@ func (siw *ServerInterfaceWrapper) DeleteMergeRequest(w http.ResponseWriter, r * handler.ServeHTTP(w, r.WithContext(ctx)) } -// GetMergeRequest operation middleware -func (siw *ServerInterfaceWrapper) GetMergeRequest(w http.ResponseWriter, r *http.Request) { +// CreateMergeRequest operation middleware +func (siw *ServerInterfaceWrapper) CreateMergeRequest(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error + // ------------- Body parse ------------- + var body CreateMergeRequestJSONRequestBody + parseBody := true + if parseBody { + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "Error unmarshalling body 'CreateMergeRequest' as JSON", http.StatusBadRequest) + return + } + } + // ------------- Path parameter "owner" ------------- var owner string @@ -6054,15 +6340,6 @@ func (siw *ServerInterfaceWrapper) GetMergeRequest(w http.ResponseWriter, r *htt return } - // ------------- Path parameter "mrId" ------------- - var mrId uint64 - - err = runtime.BindStyledParameterWithOptions("simple", "mrId", chi.URLParam(r, "mrId"), &mrId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "mrId", Err: err}) - return - } - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) ctx = context.WithValue(ctx, Basic_authScopes, []string{}) @@ -6070,7 +6347,7 @@ func (siw *ServerInterfaceWrapper) GetMergeRequest(w http.ResponseWriter, r *htt ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetMergeRequest(r.Context(), &JiaozifsResponse{w}, r, owner, repository, mrId) + siw.Handler.CreateMergeRequest(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository) })) for _, middleware := range siw.HandlerMiddlewares { @@ -6080,18 +6357,18 @@ func (siw *ServerInterfaceWrapper) GetMergeRequest(w http.ResponseWriter, r *htt handler.ServeHTTP(w, r.WithContext(ctx)) } -// UpdateMergeRequest operation middleware -func (siw *ServerInterfaceWrapper) UpdateMergeRequest(w http.ResponseWriter, r *http.Request) { +// Merge operation middleware +func (siw *ServerInterfaceWrapper) Merge(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error // ------------- Body parse ------------- - var body UpdateMergeRequestJSONRequestBody + var body MergeJSONRequestBody parseBody := true if parseBody { if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - http.Error(w, "Error unmarshalling body 'UpdateMergeRequest' as JSON", http.StatusBadRequest) + http.Error(w, "Error unmarshalling body 'Merge' as JSON", http.StatusBadRequest) return } } @@ -6114,12 +6391,12 @@ func (siw *ServerInterfaceWrapper) UpdateMergeRequest(w http.ResponseWriter, r * return } - // ------------- Path parameter "mrId" ------------- - var mrId uint64 + // ------------- Path parameter "mrSeq" ------------- + var mrSeq uint64 - err = runtime.BindStyledParameterWithOptions("simple", "mrId", chi.URLParam(r, "mrId"), &mrId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "mrSeq", chi.URLParam(r, "mrSeq"), &mrSeq, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "mrId", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "mrSeq", Err: err}) return } @@ -6130,7 +6407,7 @@ func (siw *ServerInterfaceWrapper) UpdateMergeRequest(w http.ResponseWriter, r * ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.UpdateMergeRequest(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository, mrId) + siw.Handler.Merge(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository, mrSeq) })) for _, middleware := range siw.HandlerMiddlewares { @@ -7477,6 +7754,75 @@ func (siw *ServerInterfaceWrapper) GetWip(w http.ResponseWriter, r *http.Request handler.ServeHTTP(w, r.WithContext(ctx)) } +// UpdateWip operation middleware +func (siw *ServerInterfaceWrapper) UpdateWip(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Body parse ------------- + var body UpdateWipJSONRequestBody + parseBody := true + if parseBody { + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "Error unmarshalling body 'UpdateWip' as JSON", http.StatusBadRequest) + return + } + } + + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params UpdateWipParams + + // ------------- Required query parameter "refName" ------------- + + if paramValue := r.URL.Query().Get("refName"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UpdateWip(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // GetWipChanges operation middleware func (siw *ServerInterfaceWrapper) GetWipChanges(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -7851,19 +8197,19 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Post(options.BaseURL+"/auth/logout", wrapper.Logout) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/mergequest/{owner}/{repository}", wrapper.ListMergeRequests) + r.Get(options.BaseURL+"/mergequest/{owner}/{repository}/{mrSeq}", wrapper.GetMergeRequest) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/mergequest/{owner}/{repository}", wrapper.CreateMergeRequest) + r.Post(options.BaseURL+"/mergequest/{owner}/{repository}/{mrSeq}", wrapper.UpdateMergeRequest) }) r.Group(func(r chi.Router) { - r.Delete(options.BaseURL+"/mergequest/{owner}/{repository}/mr/{mrId}", wrapper.DeleteMergeRequest) + r.Get(options.BaseURL+"/mergerequest/{owner}/{repository}", wrapper.ListMergeRequests) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/mergequest/{owner}/{repository}/mr/{mrId}", wrapper.GetMergeRequest) + r.Post(options.BaseURL+"/mergerequest/{owner}/{repository}", wrapper.CreateMergeRequest) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/mergequest/{owner}/{repository}/mr/{mrId}", wrapper.UpdateMergeRequest) + r.Post(options.BaseURL+"/mergerequest/{owner}/{repository}/{mrSeq}/merge", wrapper.Merge) }) r.Group(func(r chi.Router) { r.Delete(options.BaseURL+"/object/{owner}/{repository}", wrapper.DeleteObject) @@ -7937,6 +8283,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/wip/{owner}/{repository}", wrapper.GetWip) }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/wip/{owner}/{repository}", wrapper.UpdateWip) + }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/wip/{owner}/{repository}/changes", wrapper.GetWipChanges) }) @@ -7956,83 +8305,87 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "/y6wKrB1fQAA", + "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", + "7v8DAAD//yVnSbY2hwAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index e0dd5150..5e762c12 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -164,14 +164,17 @@ components: type: string description: type: string + status: + type: integer + format: int MergeMergeRequest: type: object required: - msg - - conflict_resolve properties: msg: type: string + allowEmptyValue: true conflict_resolve: description: use to record the resolution of the conflict, example({"b/a.txt":"base"}) type: object @@ -181,6 +184,7 @@ components: type: object required: - id + - sequence - target_branch - source_branch - source_repo_id @@ -192,6 +196,9 @@ components: - updated_at properties: id: + type: string + format: uuid + sequence: type: integer format: uint64 target_branch: @@ -226,6 +233,7 @@ components: type: object required: - id + - sequence - target_branch - source_branch - source_repo_id @@ -238,6 +246,9 @@ components: - updated_at properties: id: + type: string + format: uuid + sequence: type: integer format: uint64 target_branch: @@ -1633,7 +1644,7 @@ paths: 403: description: Forbidden - /mergequest/{owner}/{repository}: + /mergerequest/{owner}/{repository}: parameters: - in: path name: owner @@ -1651,9 +1662,14 @@ paths: operationId: listMergeRequests summary: get list of merge request in repository parameters: - - $ref: "#/components/parameters/PaginationPrefix" - $ref: "#/components/parameters/PaginationRepoAfter" - $ref: "#/components/parameters/PaginationAmount" + - in: query + name: state + required: false + schema: + type: integer + format: int responses: 200: description: merge request @@ -1696,7 +1712,7 @@ paths: 500: description: Internal Server Error - /mergequest/{owner}/{repository}/{mrId}/merge: + /mergerequest/{owner}/{repository}/{mrSeq}/merge: parameters: - in: path name: owner @@ -1709,7 +1725,7 @@ paths: schema: type: string - in: path - name: mrId + name: mrSeq required: true schema: type: integer @@ -1742,7 +1758,7 @@ paths: description: Too many requests 500: description: Internal Server Error - /mergequest/{owner}/{repository}/{mrId}: + /mergequest/{owner}/{repository}/{mrSeq}: parameters: - in: path name: owner @@ -1755,7 +1771,7 @@ paths: schema: type: string - in: path - name: mrId + name: mrSeq required: true schema: type: integer @@ -1780,22 +1796,6 @@ paths: description: Too many requests 500: description: Internal Server Error - delete: - tags: - - mergerequest - operationId: deleteMergeRequest - summary: delete merge request - responses: - 204: - description: delete merge request successfully - 401: - description: Unauthorized - 404: - description: Resource Not Found - 420: - description: Too many requests - 500: - description: Internal Server Error post: tags: - mergerequest diff --git a/controller/merge_request_ctl.go b/controller/merge_request_ctl.go index 42c5a603..565d3c1f 100644 --- a/controller/merge_request_ctl.go +++ b/controller/merge_request_ctl.go @@ -2,6 +2,7 @@ package controller import ( "context" + "errors" "fmt" "net/http" "time" @@ -51,6 +52,9 @@ func (mrCtl MergeRequestController) ListMergeRequests(ctx context.Context, w *ap } listParams := models.NewListMergeRequestParams().SetTargetRepoID(repository.ID) + if params.State != nil { + listParams.SetMergeState(models.MergeState(*params.State)) + } if params.After != nil { listParams.SetAfter(*params.After) @@ -71,14 +75,13 @@ func (mrCtl MergeRequestController) ListMergeRequests(ctx context.Context, w *ap results := make([]api.MergeRequest, len(mrs)) for index, mr := range mrs { results[index] = api.MergeRequest{ - Id: mr.ID, Title: mr.Title, Description: mr.Description, AuthorId: mr.AuthorID, - MergeStatus: int(mr.MergeStatus), - SourceBranch: mr.SourceBranch, + MergeStatus: int(mr.MergeState), + SourceBranch: mr.SourceBranchID, SourceRepoId: mr.SourceRepoID, - TargetBranch: mr.TargetBranch, + TargetBranch: mr.TargetBranchID, TargetRepoId: mr.TargetRepoID, CreatedAt: mr.CreatedAt, UpdatedAt: mr.UpdatedAt, @@ -123,10 +126,11 @@ func (mrCtl MergeRequestController) CreateMergeRequest(ctx context.Context, w *a } if body.SourceBranchName == body.TargetBranchName { - w.BadRequest(fmt.Sprintf("source branch name %s and target branch name %s can not be same", body.SourceBranchName, body.TargetBranchName)) + w.BadRequest(fmt.Sprintf("source branch name %s and target branch name %s can not be same", body.SourceBranchName, body.SourceBranchName)) + return } - sorceBranch, err := mrCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(body.SourceBranchName)) + sourceBranch, err := mrCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(body.SourceBranchName)) if err != nil { w.Error(err) return @@ -138,17 +142,30 @@ func (mrCtl MergeRequestController) CreateMergeRequest(ctx context.Context, w *a return } + params := models.NewGetMergeRequestParams().SetTargetRepo(repository.ID).SetTargetBranch(targetBranch.ID).SetSourceBranch(sourceBranch.ID).SetState(models.MergeStateInit) + mr, err := mrCtl.Repo.MergeRequestRepo().Get(ctx, params) + if err == nil { + fmt.Println(mr) + w.BadRequest(fmt.Sprintf("repo %s merge request between %s and %s already exists", repositoryName, body.SourceBranchName, body.TargetBranchName)) + return + } + + if err != nil && !errors.Is(err, models.ErrNotFound) { + w.Error(err) + return + } + mrModel, err := mrCtl.Repo.MergeRequestRepo().Insert(ctx, &models.MergeRequest{ - TargetBranch: targetBranch.ID, - SourceBranch: sorceBranch.ID, - SourceRepoID: repository.ID, - TargetRepoID: repository.ID, - Title: body.Title, - MergeStatus: models.InitMergeStatus, - Description: body.Description, - AuthorID: operator.ID, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), + TargetBranchID: targetBranch.ID, + SourceBranchID: sourceBranch.ID, + SourceRepoID: repository.ID, + TargetRepoID: repository.ID, + Title: body.Title, + MergeState: models.MergeStateInit, + Description: body.Description, + AuthorID: operator.ID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), }) if err != nil { @@ -162,7 +179,7 @@ func (mrCtl MergeRequestController) CreateMergeRequest(ctx context.Context, w *a return } - err = workRepo.CheckOut(ctx, versionmgr.InBranch, sorceBranch.Name) + err = workRepo.CheckOut(ctx, versionmgr.InBranch, sourceBranch.Name) if err != nil { w.Error(err) return @@ -176,13 +193,14 @@ func (mrCtl MergeRequestController) CreateMergeRequest(ctx context.Context, w *a resp := api.MergeRequestFullState{ Id: mrModel.ID, + Sequence: mrModel.Sequence, Title: mrModel.Title, Description: mrModel.Description, AuthorId: mrModel.AuthorID, - MergeStatus: int(mrModel.MergeStatus), - SourceBranch: mrModel.SourceBranch, + MergeStatus: int(mrModel.MergeState), + SourceBranch: mrModel.SourceBranchID, SourceRepoId: mrModel.SourceRepoID, - TargetBranch: mrModel.TargetBranch, + TargetBranch: mrModel.TargetBranchID, TargetRepoId: mrModel.TargetRepoID, CreatedAt: mrModel.CreatedAt, UpdatedAt: mrModel.UpdatedAt, @@ -196,38 +214,7 @@ func (mrCtl MergeRequestController) CreateMergeRequest(ctx context.Context, w *a //get merge state w.JSON(mrModel, http.StatusCreated) } - -func (mrCtl MergeRequestController) DeleteMergeRequest(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, _ string, mrID uint64) { - operator, err := auth.GetOperator(ctx) - if err != nil { - w.Error(err) - return - } - - owner, err := mrCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) - if err != nil { - w.Error(err) - return - } - - if operator.Name != owner.Name { - w.Forbidden() - return - } - - affectRows, err := mrCtl.Repo.MergeRequestRepo().Delete(ctx, models.NewDeleteMergeRequestParams().SetID(mrID)) - if err != nil { - w.Error(err) - return - } - if affectRows == 0 { - w.NotFound() - return - } - w.OK() -} - -func (mrCtl MergeRequestController) GetMergeRequest(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, mrID uint64) { +func (mrCtl MergeRequestController) GetMergeRequest(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, mrSeq uint64) { operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) @@ -245,7 +232,6 @@ func (mrCtl MergeRequestController) GetMergeRequest(ctx context.Context, w *api. return } - // Get repo repository, err := mrCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) if err != nil { w.Error(err) @@ -258,19 +244,19 @@ func (mrCtl MergeRequestController) GetMergeRequest(ctx context.Context, w *api. return } - mergeRequest, err := mrCtl.Repo.MergeRequestRepo().Get(ctx, models.NewGetMergeRequestParams().SetID(mrID)) + mergeRequest, err := mrCtl.Repo.MergeRequestRepo().Get(ctx, models.NewGetMergeRequestParams().SetTargetRepo(repository.ID).SetNumber(mrSeq)) if err != nil { w.Error(err) return } - sourceBranch, err := mrCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetID(mergeRequest.SourceRepoID)) + sourceBranch, err := mrCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetID(mergeRequest.SourceBranchID)) if err != nil { w.Error(err) return } - targetBranch, err := mrCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetID(mergeRequest.TargetBranch)) + targetBranch, err := mrCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetID(mergeRequest.TargetBranchID)) if err != nil { w.Error(err) return @@ -290,13 +276,14 @@ func (mrCtl MergeRequestController) GetMergeRequest(ctx context.Context, w *api. resp := api.MergeRequestFullState{ Id: mergeRequest.ID, + Sequence: mergeRequest.Sequence, Title: mergeRequest.Title, Description: mergeRequest.Description, AuthorId: mergeRequest.AuthorID, - MergeStatus: int(mergeRequest.MergeStatus), - SourceBranch: mergeRequest.SourceBranch, + MergeStatus: int(mergeRequest.MergeState), + SourceBranch: mergeRequest.SourceBranchID, SourceRepoId: mergeRequest.SourceRepoID, - TargetBranch: mergeRequest.TargetBranch, + TargetBranch: mergeRequest.TargetBranchID, TargetRepoId: mergeRequest.TargetRepoID, CreatedAt: mergeRequest.CreatedAt, UpdatedAt: mergeRequest.UpdatedAt, @@ -309,7 +296,7 @@ func (mrCtl MergeRequestController) GetMergeRequest(ctx context.Context, w *api. w.JSON(resp) } -func (mrCtl MergeRequestController) UpdateMergeRequest(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.UpdateMergeRequestJSONRequestBody, ownerName string, repositoryName string, mrID uint64) { +func (mrCtl MergeRequestController) UpdateMergeRequest(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.UpdateMergeRequestJSONRequestBody, ownerName string, repositoryName string, mrSeq uint64) { operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) @@ -326,14 +313,22 @@ func (mrCtl MergeRequestController) UpdateMergeRequest(ctx context.Context, w *a w.Forbidden() return } + repository, err := mrCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) + if err != nil { + w.Error(err) + return + } - updateParams := models.NewUpdateMergeRequestParams(mrID) + updateParams := models.NewUpdateMergeRequestParams(repository.ID, mrSeq) if body.Title != nil { updateParams.SetTitle(utils.StringValue(body.Title)) } if body.Description != nil { updateParams.SetDescription(utils.StringValue(body.Description)) } + if body.Status != nil { + updateParams.SetState(models.MergeState(utils.IntValue(body.Status))) + } err = mrCtl.Repo.MergeRequestRepo().UpdateByID(ctx, updateParams) if err != nil { @@ -343,7 +338,7 @@ func (mrCtl MergeRequestController) UpdateMergeRequest(ctx context.Context, w *a w.OK() } -func (mrCtl MergeRequestController) Merge(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.MergeJSONRequestBody, ownerName string, repositoryName string, mrID uint64) { +func (mrCtl MergeRequestController) Merge(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.MergeJSONRequestBody, ownerName string, repositoryName string, mrSeq uint64) { operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) @@ -368,42 +363,47 @@ func (mrCtl MergeRequestController) Merge(ctx context.Context, w *api.JiaozifsRe return } - mergeRequest, err := mrCtl.Repo.MergeRequestRepo().Get(ctx, models.NewGetMergeRequestParams().SetID(mrID)) + mergeRequest, err := mrCtl.Repo.MergeRequestRepo().Get(ctx, models.NewGetMergeRequestParams().SetTargetRepo(repository.ID).SetNumber(mrSeq)) if err != nil { w.Error(err) return } - workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repository, mrCtl.Repo, mrCtl.PublicStorageConfig) - if err != nil { - w.Error(err) - return - } + var commit *models.Commit + err = mrCtl.Repo.Transaction(ctx, func(repo models.IRepo) error { + workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repository, mrCtl.Repo, mrCtl.PublicStorageConfig) + if err != nil { + return err + } - sourceBranch, err := mrCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetID(mergeRequest.SourceRepoID)) - if err != nil { - w.Error(err) - return - } + sourceBranch, err := mrCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetID(mergeRequest.SourceBranchID)) + if err != nil { + return err + } - targetBranch, err := mrCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetID(mergeRequest.TargetBranch)) - if err != nil { - w.Error(err) - return - } + targetBranch, err := mrCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetID(mergeRequest.TargetBranchID)) + if err != nil { + return err + } - err = workRepo.CheckOut(ctx, versionmgr.InBranch, sourceBranch.Name) - if err != nil { - w.Error(err) - return - } + err = workRepo.CheckOut(ctx, versionmgr.InBranch, sourceBranch.Name) + if err != nil { + return err + } - merge, err := workRepo.Merge(ctx, targetBranch.CommitHash, body.Msg, versionmgr.ResolveFromSelector(body.ConflictResolve)) + commit, err = workRepo.Merge(ctx, targetBranch.CommitHash, body.Msg, versionmgr.ResolveFromSelector(utils.Map(body.ConflictResolve))) + if err != nil { + return err + } + + return mrCtl.Repo.MergeRequestRepo().UpdateByID(ctx, models.NewUpdateMergeRequestParams(repository.ID, mergeRequest.Sequence).SetState(models.MergeStateMerged)) + }) if err != nil { w.Error(err) return } - w.JSON(merge) + + w.JSON(commit) } func changePairToDTO(pairs []*versionmgr.ChangePair) ([]api.ChangePair, error) { @@ -448,10 +448,10 @@ func changePairToDTO(pairs []*versionmgr.ChangePair) ([]api.ChangePair, error) { Path: path, } if ch.Right.From() != nil { - pair.Left.BaseHash = utils.String(hash.Hash(ch.Right.From().Hash()).Hex()) + pair.Right.BaseHash = utils.String(hash.Hash(ch.Right.From().Hash()).Hex()) } if ch.Right.To() != nil { - pair.Left.ToHash = utils.String(hash.Hash(ch.Right.To().Hash()).Hex()) + pair.Right.ToHash = utils.String(hash.Hash(ch.Right.To().Hash()).Hex()) } } changes[index] = pair diff --git a/controller/wip_ctl.go b/controller/wip_ctl.go index ec1728cc..5f38c992 100644 --- a/controller/wip_ctl.go +++ b/controller/wip_ctl.go @@ -151,7 +151,7 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon w.JSON(workRepo.CurWip(), http.StatusCreated) } -func (wipCtl WipController) UpdateWip(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, body api.UpdateWipJSONRequestBody, ownerName string, repositoryName string, params api.UpdateWipParams) { +func (wipCtl WipController) UpdateWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.UpdateWipJSONRequestBody, ownerName string, repositoryName string, params api.UpdateWipParams) { operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) diff --git a/integrationtest/branch_test.go b/integrationtest/branch_test.go index 3fda89f8..384cf1f2 100644 --- a/integrationtest/branch_test.go +++ b/integrationtest/branch_test.go @@ -151,7 +151,7 @@ func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { }) }) - createBranch(ctx, c, client, userName, repoName, "main", "feat/sec_branch") + createBranch(ctx, c, client, "create sec branch", userName, repoName, "main", "feat/sec_branch") c.Convey("list branch", func(c convey.C) { c.Convey("no auth", func() { diff --git a/integrationtest/commit_test.go b/integrationtest/commit_test.go index 122b6380..65bbc538 100644 --- a/integrationtest/commit_test.go +++ b/integrationtest/commit_test.go @@ -23,7 +23,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { createUser(ctx, c, client, userName) loginAndSwitch(ctx, c, client, "kitty login", userName, false) createRepo(ctx, c, client, repoName) - createBranch(ctx, c, client, userName, repoName, "main", branchName) + createBranch(ctx, c, client, "create branch", userName, repoName, "main", branchName) createWip(ctx, c, client, "feat get entries test0", userName, repoName, branchName) uploadObject(ctx, c, client, "update f1 to test branch", userName, repoName, branchName, "m.dat") uploadObject(ctx, c, client, "update f2 to test branch", userName, repoName, branchName, "g/x.dat") @@ -253,7 +253,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("success to get entries in empty hash", func() { resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ Path: utils.String("/"), - Ref: utils.String(hash.EmptyHash.Hex()), + Ref: utils.String(hash.Empty.Hex()), Type: api.RefTypeCommit, }) convey.So(err, convey.ShouldBeNil) diff --git a/integrationtest/helper_test.go b/integrationtest/helper_test.go index 6b099f68..600e0748 100644 --- a/integrationtest/helper_test.go +++ b/integrationtest/helper_test.go @@ -13,6 +13,8 @@ import ( "testing" "time" + "github.com/jiaozifs/jiaozifs/utils" + "github.com/jiaozifs/jiaozifs/api" "github.com/smartystreets/goconvey/convey" @@ -139,8 +141,8 @@ func loginAndSwitch(ctx context.Context, c convey.C, client *api.Client, title, }) } -func createBranch(ctx context.Context, c convey.C, client *api.Client, user string, repoName string, source, refName string) { - c.Convey("create branch "+refName, func() { +func createBranch(ctx context.Context, c convey.C, client *api.Client, title string, user string, repoName string, source, refName string) { + c.Convey("create branch "+title, func() { resp, err := client.CreateBranch(ctx, user, repoName, api.CreateBranchJSONRequestBody{ Source: source, Name: refName, @@ -205,3 +207,16 @@ func commitWip(ctx context.Context, c convey.C, client *api.Client, title string convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) }) } + +func createMergeRequest(ctx context.Context, c convey.C, client *api.Client, title string, user string, repoName string, sourceBranch string, targetBranch string) { + c.Convey("create mr "+title, func() { + resp, err := client.CreateMergeRequest(ctx, user, repoName, api.CreateMergeRequestJSONRequestBody{ + Description: utils.String("create merge request test"), + SourceBranchName: sourceBranch, + TargetBranchName: targetBranch, + Title: "Merge: test", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + }) +} diff --git a/integrationtest/merge_request_test.go b/integrationtest/merge_request_test.go index 8169a8f9..578b9bfd 100644 --- a/integrationtest/merge_request_test.go +++ b/integrationtest/merge_request_test.go @@ -2,7 +2,12 @@ package integrationtest import ( "context" + "fmt" "net/http" + "strconv" + "time" + + "github.com/jiaozifs/jiaozifs/models" "github.com/jiaozifs/jiaozifs/utils" @@ -13,6 +18,7 @@ import ( func MergeRequestSpec(ctx context.Context, urlStr string) func(c convey.C) { client, _ := api.NewClient(urlStr + apiimpl.APIV1Prefix) + var firstMrID *uint64 return func(c convey.C) { userName := "navi" repoName := "mr_test" @@ -21,25 +27,388 @@ func MergeRequestSpec(ctx context.Context, urlStr string) func(c convey.C) { createUser(ctx, c, client, userName) loginAndSwitch(ctx, c, client, "molly login", userName, false) createRepo(ctx, c, client, repoName) - createBranch(ctx, c, client, userName, repoName, "main", branchName) + createBranch(ctx, c, client, "create branch", userName, repoName, "main", branchName) createWip(ctx, c, client, "feat get obj test", userName, repoName, branchName) uploadObject(ctx, c, client, "update f1 to test branch", userName, repoName, branchName, "a.bin") - commitWip(ctx, c, client, "commit delete object", userName, repoName, branchName, "test") + commitWip(ctx, c, client, "commit object", userName, repoName, branchName, "test") c.Convey("create merge request", func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors client.RequestEditors = nil + resp, err := client.CreateMergeRequest(ctx, userName, repoName, api.CreateMergeRequestJSONRequestBody{ + Description: utils.String("create merge request test"), + SourceBranchName: branchName, + TargetBranchName: "main", + Title: "Merge: test", + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("fail to create mergerequest in non exit repo", func() { + resp, err := client.CreateMergeRequest(ctx, userName, "fakerepo", api.CreateMergeRequestJSONRequestBody{ + Description: utils.String("create merge request test"), + SourceBranchName: branchName, + TargetBranchName: "main", + Title: "Merge: test", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to create mergerequest from non exit user", func() { + resp, err := client.CreateMergeRequest(ctx, "mock_user", repoName, api.CreateMergeRequestJSONRequestBody{ + Description: utils.String("create merge request test"), + SourceBranchName: branchName, + TargetBranchName: "main", + Title: "Merge: test", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to create mergerequest from non exit repo", func() { + resp, err := client.CreateMergeRequest(ctx, userName, "fakerepo", api.CreateMergeRequestJSONRequestBody{ + Description: utils.String("create merge request test"), + SourceBranchName: branchName, + TargetBranchName: "main", + Title: "Merge: test", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to create mergerequest from others user", func() { + resp, err := client.CreateMergeRequest(ctx, "jimmy", "happygo", api.CreateMergeRequestJSONRequestBody{ + Description: utils.String("create merge request test"), + SourceBranchName: branchName, + TargetBranchName: "main", + Title: "Merge: test", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + + c.Convey("fail to create mergerequest from non exit branch", func() { + resp, err := client.CreateMergeRequest(ctx, userName, repoName, api.CreateMergeRequestJSONRequestBody{ + Description: utils.String("create merge request test"), + SourceBranchName: "fakeb1", + TargetBranchName: "fakeb2", + Title: "Merge: test", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to create mergerequest from non exit branch 2", func() { + resp, err := client.CreateMergeRequest(ctx, userName, repoName, api.CreateMergeRequestJSONRequestBody{ + Description: utils.String("create merge request test"), + SourceBranchName: branchName, + TargetBranchName: "fakeb2", + Title: "Merge: test", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to create mergerequest from same source and target branch", func() { resp, err := client.CreateMergeRequest(ctx, userName, repoName, api.CreateMergeRequestJSONRequestBody{ Description: utils.String("create merge request test"), SourceBranchName: branchName, TargetBranchName: branchName, Title: "Merge: test", }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + + c.Convey("success to create mergerequest", func() { + resp, err := client.CreateMergeRequest(ctx, userName, repoName, api.CreateMergeRequestJSONRequestBody{ + Description: utils.String("create merge request test"), + SourceBranchName: branchName, + TargetBranchName: "main", + Title: "Merge: test", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + + createResp, err := api.ParseCreateMergeRequestResponse(resp) + convey.So(err, convey.ShouldBeNil) + firstMrID = &createResp.JSON201.Sequence + }) + c.Convey("fail to create dup mergerequest", func() { + resp, err := client.CreateMergeRequest(ctx, userName, repoName, api.CreateMergeRequestJSONRequestBody{ + Description: utils.String("create merge request test"), + SourceBranchName: branchName, + TargetBranchName: "main", + Title: "Merge: test", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + }) + + c.Convey("get merge request", func(c convey.C) { + + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.GetMergeRequest(ctx, userName, repoName, *firstMrID) client.RequestEditors = re convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) + + c.Convey("fail to get merge request in non exit repo", func() { + resp, err := client.GetMergeRequest(ctx, userName, "fakerepo", *firstMrID) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to get merge request from non exit user", func() { + resp, err := client.GetMergeRequest(ctx, "mock_user", repoName, *firstMrID) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to get merge request from others user", func() { + resp, err := client.GetMergeRequest(ctx, "jimmy", "happygo", *firstMrID) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + + c.Convey("fail to get merge request with non exit mergerequest", func() { + resp, err := client.GetMergeRequest(ctx, userName, repoName, 1000) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("success to get merge request", func() { + resp, err := client.GetMergeRequest(ctx, userName, repoName, *firstMrID) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + }) + }) + + c.Convey("update merge request", func(c convey.C) { + + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.UpdateMergeRequest(ctx, userName, repoName, *firstMrID, api.UpdateMergeRequestJSONRequestBody{ + Description: utils.String("update merge request test"), + Title: utils.String("Merge: test title"), + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("fail to update merge request in non exit repo", func() { + resp, err := client.UpdateMergeRequest(ctx, userName, "fakerepo", *firstMrID, api.UpdateMergeRequestJSONRequestBody{ + Description: utils.String("update merge request test"), + Title: utils.String("Merge: test title"), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to update merge request from non exit user", func() { + resp, err := client.UpdateMergeRequest(ctx, "mockuser", repoName, *firstMrID, api.UpdateMergeRequestJSONRequestBody{ + Description: utils.String("update merge request test"), + Title: utils.String("Merge: test title"), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to update merge request from others user", func() { + resp, err := client.UpdateMergeRequest(ctx, "jimmy", "happygo", *firstMrID, api.UpdateMergeRequestJSONRequestBody{ + Description: utils.String("update merge request test"), + Title: utils.String("Merge: test title"), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + + c.Convey("success to update merge request", func() { + resp, err := client.UpdateMergeRequest(ctx, userName, repoName, *firstMrID, api.UpdateMergeRequestJSONRequestBody{ + Description: utils.String("update merge request test"), + Title: utils.String("Merge: test title"), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + getResp, err := client.GetMergeRequest(ctx, userName, repoName, *firstMrID) + convey.So(err, convey.ShouldBeNil) + convey.So(getResp.StatusCode, convey.ShouldEqual, http.StatusOK) + + updatedResult, err := api.ParseGetMergeRequestResponse(getResp) + convey.So(err, convey.ShouldBeNil) + convey.So("Merge: test title", convey.ShouldEqual, (*updatedResult.JSON200).Title) + convey.So("update merge request test", convey.ShouldEqual, *(*updatedResult.JSON200).Description) + }) + }) + + for i := 0; i < 10; i++ { + branchName := fmt.Sprintf("feat/list_merge_test_%d", i) + createBranch(ctx, c, client, fmt.Sprintf("create branch %d", i), userName, repoName, "main", branchName) + createWip(ctx, c, client, "feat list merge test "+strconv.Itoa(i), userName, repoName, branchName) + uploadObject(ctx, c, client, "update f1 to test branch "+strconv.Itoa(i), userName, repoName, branchName, fmt.Sprintf("%d.txt", i)) + commitWip(ctx, c, client, "commit object "+strconv.Itoa(i), userName, repoName, branchName, "test") + createMergeRequest(ctx, c, client, "create merge request "+strconv.Itoa(i), userName, repoName, branchName, "main") + } + + c.Convey("list merge request", func(c convey.C) { + + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.ListMergeRequests(ctx, userName, repoName, &api.ListMergeRequestsParams{}) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("fail to list merge request in non exit repo", func() { + resp, err := client.ListMergeRequests(ctx, userName, "fakerepo", &api.ListMergeRequestsParams{}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to list merge request from non exit user", func() { + resp, err := client.ListMergeRequests(ctx, "mockuser", repoName, &api.ListMergeRequestsParams{}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to list merge request from others user", func() { + resp, err := client.ListMergeRequests(ctx, "jimmy", "happygo", &api.ListMergeRequestsParams{}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + + c.Convey("success to list merge request", func() { + resp, err := client.ListMergeRequests(ctx, userName, repoName, &api.ListMergeRequestsParams{}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseListMergeRequestsResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.ShouldHaveLength(*result.JSON200, 11) + }) + + c.Convey("success to list merge request over max page", func() { + resp, err := client.ListMergeRequests(ctx, userName, repoName, &api.ListMergeRequestsParams{ + Amount: utils.Int(-1), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseListMergeRequestsResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.ShouldHaveLength(*result.JSON200, 11) + }) + + c.Convey("success to list by state merge request", func() { + resp, err := client.ListMergeRequests(ctx, userName, repoName, &api.ListMergeRequestsParams{ + State: utils.Int(int(models.MergeStateClosed)), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseListMergeRequestsResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.ShouldHaveLength(*result.JSON200, 0) + }) + + c.Convey("success to list page merge quest", func() { + var after *time.Time + for i := 0; i < 6; i++ { + resp, err := client.ListMergeRequests(ctx, userName, repoName, &api.ListMergeRequestsParams{ + After: after, + Amount: utils.Int(2), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseListMergeRequestsResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.ShouldHaveLength(*result.JSON200, 2) + if i > 5 { + convey.ShouldBeFalse((*result.JSON200).Pagination.HasMore) + } else { + convey.ShouldBeTrue((*result.JSON200).Pagination.HasMore) + } + fmt.Println((*result.JSON200).Pagination.NextOffset) + next, err := time.Parse(`2006-01-02 15:04:05.000000 +0800 CST`, (*result.JSON200).Pagination.NextOffset) + convey.So(err, convey.ShouldBeNil) + after = &next + } + }) + }) + + c.Convey(" merge request", func(c convey.C) { + + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.Merge(ctx, userName, repoName, *firstMrID, api.MergeJSONRequestBody{ + Msg: "test merge", + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("fail to update merge request in non exit repo", func() { + resp, err := client.Merge(ctx, userName, "fakerepo", *firstMrID, api.MergeJSONRequestBody{ + Msg: "test merge", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to update merge request from non exit user", func() { + resp, err := client.Merge(ctx, "mockuser", repoName, *firstMrID, api.MergeJSONRequestBody{ + Msg: "test merge", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to update merge request from others user", func() { + resp, err := client.Merge(ctx, "jimmy", "happygo", *firstMrID, api.MergeJSONRequestBody{ + Msg: "test merge", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + }) + c.Convey("fail to update merge request from non exit mr", func() { + resp, err := client.Merge(ctx, userName, repoName, 100, api.MergeJSONRequestBody{ + Msg: "test merge", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("success to update merge request", func() { + resp, err := client.Merge(ctx, userName, repoName, *firstMrID, api.MergeJSONRequestBody{ + Msg: "test merge", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + getResp, err := client.GetMergeRequest(ctx, userName, repoName, *firstMrID) + convey.So(err, convey.ShouldBeNil) + convey.So(getResp.StatusCode, convey.ShouldEqual, http.StatusOK) + + updatedResult, err := api.ParseGetMergeRequestResponse(getResp) + convey.So(err, convey.ShouldBeNil) + convey.So(int(models.MergeStateMerged), convey.ShouldEqual, (*updatedResult.JSON200).MergeStatus) + }) }) } } diff --git a/integrationtest/objects_test.go b/integrationtest/objects_test.go index 9a7153c5..a4c71f8f 100644 --- a/integrationtest/objects_test.go +++ b/integrationtest/objects_test.go @@ -24,7 +24,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { createUser(ctx, c, client, userName) loginAndSwitch(ctx, c, client, "molly login", userName, false) createRepo(ctx, c, client, repoName) - createBranch(ctx, c, client, userName, repoName, "main", branchName) + createBranch(ctx, c, client, "create branch", userName, repoName, "main", branchName) createWip(ctx, c, client, "feat get obj test", userName, repoName, branchName) c.Convey("upload object", func(c convey.C) { diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index 2cecc89b..9698c9e0 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -351,7 +351,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) }) - createBranch(ctx, c, client, userName, repoName, "main", "feat/ano_branch") + createBranch(ctx, c, client, "create branch", userName, repoName, "main", "feat/ano_branch") c.Convey("update default head success", func() { resp, err := client.UpdateRepository(ctx, userName, repoName, api.UpdateRepositoryJSONRequestBody{ Head: utils.String("feat/ano_branch"), diff --git a/integrationtest/wip_object_test.go b/integrationtest/wip_object_test.go index cc1fdc02..ee20dd1c 100644 --- a/integrationtest/wip_object_test.go +++ b/integrationtest/wip_object_test.go @@ -22,7 +22,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { createUser(ctx, c, client, userName) loginAndSwitch(ctx, c, client, "jude login", userName, false) createRepo(ctx, c, client, repoName) - createBranch(ctx, c, client, userName, repoName, "main", branchName) + createBranch(ctx, c, client, "create branch", userName, repoName, "main", branchName) createWip(ctx, c, client, "get wip obj test", userName, repoName, branchName) uploadObject(ctx, c, client, "update f1 to test branch", userName, repoName, branchName, "m.dat") uploadObject(ctx, c, client, "update f2 to test branch", userName, repoName, branchName, "g/m.dat") @@ -298,7 +298,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { uploadObject(ctx, c, client, "update f6 to test branch", userName, repoName, branchName, "c.dat") testBranchName := "test/empty_branch" - createBranch(ctx, c, client, userName, repoName, "main", testBranchName) + createBranch(ctx, c, client, "create empty branch", userName, repoName, "main", testBranchName) createWip(ctx, c, client, "create empty_branch wip", userName, repoName, testBranchName) c.Convey("get wip success on init", func(c convey.C) { diff --git a/integrationtest/wip_test.go b/integrationtest/wip_test.go index 384423d7..add3eaa4 100644 --- a/integrationtest/wip_test.go +++ b/integrationtest/wip_test.go @@ -20,8 +20,8 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { createUser(ctx, c, client, userName) loginAndSwitch(ctx, c, client, "july login", userName, false) createRepo(ctx, c, client, repoName) - createBranch(ctx, c, client, userName, repoName, "main", branchName) - createBranch(ctx, c, client, userName, repoName, "main", branchNameForDelete) + createBranch(ctx, c, client, "create branch", userName, repoName, "main", branchName) + createBranch(ctx, c, client, "create branch for delete", userName, repoName, "main", branchNameForDelete) c.Convey("list non exit wip", func(c convey.C) { resp, err := client.ListWip(ctx, userName, repoName) convey.So(err, convey.ShouldBeNil) diff --git a/models/merge_request.go b/models/merge_request.go index 5a28c821..6052fac0 100644 --- a/models/merge_request.go +++ b/models/merge_request.go @@ -8,22 +8,26 @@ import ( "github.com/uptrace/bun" ) -type MergeStatus int +type MergeState int const ( - InitMergeStatus MergeStatus = 1 + MergeStateInit MergeState = 1 + MergeStateMerged MergeState = 2 + MergeStateClosed MergeState = 3 ) type MergeRequest struct { bun.BaseModel `bun:"table:merge_requests"` - ID uint64 `bun:"id,pk,autoincrement" json:"id"` - TargetBranch uuid.UUID `bun:"target_branch,unique:ts_repo_ts_branch,type:bytea,notnull" json:"target_branch"` - SourceBranch uuid.UUID `bun:"source_branch,unique:ts_repo_ts_branch,type:bytea,notnull" json:"source_branch"` - SourceRepoID uuid.UUID `bun:"source_repo_id,unique:ts_repo_ts_branch,type:bytea,notnull" json:"source_repo_id"` - TargetRepoID uuid.UUID `bun:"target_repo_id,unique:ts_repo_ts_branch,type:bytea,notnull" json:"target_repo_id"` - Title string `bun:"title,notnull" json:"title"` - MergeStatus MergeStatus `bun:"merge_status,notnull" json:"merge_status"` - Description *string `bun:"description" json:"description"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()" json:"id"` + Sequence uint64 `bun:"mr_sequence,unique:target_seq,notnull" json:"sequence"` + SourceRepoID uuid.UUID `bun:"source_repo_id,type:bytea,notnull" json:"source_repo_id"` + TargetRepoID uuid.UUID `bun:"target_repo_id,unique:target_seq,type:bytea,notnull" json:"target_repo_id"` + + TargetBranchID uuid.UUID `bun:"target_branch_id,type:bytea,notnull" json:"target_branch_id"` + SourceBranchID uuid.UUID `bun:"source_branch_id,type:bytea,notnull" json:"source_branch_id"` + Title string `bun:"title,notnull" json:"title"` + MergeState MergeState `bun:"merge_state,notnull" json:"merge_state"` + Description *string `bun:"description" json:"description"` AuthorID uuid.UUID `bun:"author_id,type:bytea,notnull" json:"author_id"` @@ -32,41 +36,80 @@ type MergeRequest struct { } type GetMergeRequestParams struct { - ID *uint64 + id uuid.UUID + sequence *uint64 + targetRepoID uuid.UUID + targetBranchID uuid.UUID + sourceBranchID uuid.UUID + state *MergeState } func NewGetMergeRequestParams() *GetMergeRequestParams { return &GetMergeRequestParams{} } -func (gmr *GetMergeRequestParams) SetID(id uint64) *GetMergeRequestParams { - gmr.ID = &id +func (gmr *GetMergeRequestParams) SetID(id uuid.UUID) *GetMergeRequestParams { + gmr.id = id + return gmr +} + +func (gmr *GetMergeRequestParams) SetNumber(sequence uint64) *GetMergeRequestParams { + gmr.sequence = &sequence + return gmr +} + +func (gmr *GetMergeRequestParams) SetTargetRepo(targetRepoID uuid.UUID) *GetMergeRequestParams { + gmr.targetRepoID = targetRepoID + return gmr +} + +func (gmr *GetMergeRequestParams) SetTargetBranch(targetBranchID uuid.UUID) *GetMergeRequestParams { + gmr.targetBranchID = targetBranchID + return gmr +} + +func (gmr *GetMergeRequestParams) SetSourceBranch(sourceBranchID uuid.UUID) *GetMergeRequestParams { + gmr.sourceBranchID = sourceBranchID + return gmr +} + +func (gmr *GetMergeRequestParams) SetState(state MergeState) *GetMergeRequestParams { + gmr.state = &state return gmr } type DeleteMergeRequestParams struct { - id *uint64 + sequence *uint64 + targetRepoID uuid.UUID } func NewDeleteMergeRequestParams() *DeleteMergeRequestParams { return &DeleteMergeRequestParams{} } -func (gmr *DeleteMergeRequestParams) SetID(id uint64) *DeleteMergeRequestParams { - gmr.id = &id - return gmr +func (dmr *DeleteMergeRequestParams) SetNumber(sequence uint64) *DeleteMergeRequestParams { + dmr.sequence = &sequence + return dmr +} + +func (dmr *DeleteMergeRequestParams) SetTargetRepo(targetRepoID uuid.UUID) *DeleteMergeRequestParams { + dmr.targetRepoID = targetRepoID + return dmr } type UpdateMergeRequestParams struct { - id *uint64 + sequence uint64 + targetRepo uuid.UUID updateTime time.Time title *string description *string + state *MergeState } -func NewUpdateMergeRequestParams(id uint64) *UpdateMergeRequestParams { +func NewUpdateMergeRequestParams(targetRepoID uuid.UUID, sequence uint64) *UpdateMergeRequestParams { return &UpdateMergeRequestParams{ - id: &id, + sequence: sequence, + targetRepo: targetRepoID, updateTime: time.Now(), } } @@ -81,10 +124,16 @@ func (u *UpdateMergeRequestParams) SetDescription(description string) *UpdateMer return u } +func (u *UpdateMergeRequestParams) SetState(state MergeState) *UpdateMergeRequestParams { + u.state = &state + return u +} + type ListMergeRequestParams struct { after *time.Time amount int targetRepoID uuid.UUID + state *MergeState } func NewListMergeRequestParams() *ListMergeRequestParams { @@ -106,6 +155,11 @@ func (lmr *ListMergeRequestParams) SetAmount(amount int) *ListMergeRequestParams return lmr } +func (lmr *ListMergeRequestParams) SetMergeState(state MergeState) *ListMergeRequestParams { + lmr.state = &state + return lmr +} + type IMergeRequestRepo interface { Insert(ctx context.Context, ref *MergeRequest) (*MergeRequest, error) Get(ctx context.Context, params *GetMergeRequestParams) (*MergeRequest, error) @@ -125,7 +179,16 @@ func NewMergeRequestRepo(db bun.IDB) IMergeRequestRepo { } func (m MergeRequestRepo) Insert(ctx context.Context, mr *MergeRequest) (*MergeRequest, error) { - _, err := m.db.NewInsert().Model(mr).Exec(ctx) + _, err := m.db.NewRaw(` + WITH INCNUMBER AS ( + SELECT MAX(mr_sequence) as max_seq from merge_requests WHERE merge_requests.target_repo_id = ? + ) + INSERT INTO merge_requests (mr_sequence, target_branch_id,source_branch_id,source_repo_id,target_repo_id,title,merge_state,description,author_id,created_at,updated_at) + SELECT COALESCE(max_seq,0)+1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? FROM INCNUMBER + RETURNING merge_requests.id, merge_requests.mr_sequence; +`, + mr.TargetRepoID, mr.TargetBranchID, mr.SourceBranchID, mr.SourceRepoID, mr.TargetRepoID, mr.Title, mr.MergeState, mr.Description, mr.AuthorID, mr.CreatedAt, mr.UpdatedAt, + ).Exec(ctx, mr) if err != nil { return nil, err } @@ -136,23 +199,46 @@ func (m MergeRequestRepo) Get(ctx context.Context, params *GetMergeRequestParams mergeRequest := &MergeRequest{} query := m.db.NewSelect().Model(mergeRequest) - if params.ID != nil { - query = query.Where("id = ?", params.ID) + if params.sequence != nil { + query = query.Where("mr_sequence = ?", params.sequence) + } + + if params.targetRepoID != uuid.Nil { + query = query.Where("target_repo_id = ?", params.targetRepoID) + } + + if params.id != uuid.Nil { + query = query.Where("id = ?", params.id) + } + + if params.state != nil { + query = query.Where("merge_state = ?", params.state) } + if params.targetBranchID != uuid.Nil { + query = query.Where("target_branch_id = ?", params.targetBranchID) + } + + if params.sourceBranchID != uuid.Nil { + query = query.Where("source_branch_id = ?", params.sourceBranchID) + } return mergeRequest, query.Limit(1).Scan(ctx) } func (m MergeRequestRepo) List(ctx context.Context, params *ListMergeRequestParams) ([]MergeRequest, bool, error) { mergeRequest := make([]MergeRequest, 0) query := m.db.NewSelect().Model(&mergeRequest) + if params.targetRepoID != uuid.Nil { query = query.Where("target_repo_id = ?", params.targetRepoID) } + if params.state != nil { + query = query.Where("merge_state = ?", params.state) + } query = query.Order("updated_at DESC") if params.after != nil { - query = query.Where("updated_at > ?", *params.after) + query = query.Where("updated_at < ?", *params.after) } err := query.Limit(params.amount).Scan(ctx) @@ -161,10 +247,12 @@ func (m MergeRequestRepo) List(ctx context.Context, params *ListMergeRequestPara func (m MergeRequestRepo) Delete(ctx context.Context, params *DeleteMergeRequestParams) (int64, error) { query := m.db.NewDelete().Model((*MergeRequest)(nil)) - if params.id != nil { - query = query.Where("id = ?", params.id) + if params.sequence != nil { + query = query.Where("mr_sequence = ?", params.sequence) + } + if params.targetRepoID != uuid.Nil { + query = query.Where("target_repo_id = ?", params.targetRepoID) } - sqlResult, err := query.Exec(ctx) if err != nil { return 0, err @@ -177,13 +265,21 @@ func (m MergeRequestRepo) Delete(ctx context.Context, params *DeleteMergeRequest } func (m MergeRequestRepo) UpdateByID(ctx context.Context, updateModel *UpdateMergeRequestParams) error { - updateQuery := m.db.NewUpdate().Model((*MergeRequest)(nil)).Where("id = ?", updateModel.id) + updateQuery := m.db.NewUpdate(). + Model((*MergeRequest)(nil)). + Where("mr_sequence = ?", updateModel.sequence). + Where("target_repo_id = ?", updateModel.targetRepo). + Set("updated_at = ?", updateModel.updateTime) + if updateModel.description != nil { updateQuery.Set("description = ?", *updateModel.description) } if updateModel.title != nil { updateQuery.Set("title = ?", *updateModel.title) } + if updateModel.state != nil { + updateQuery.Set("merge_state = ?", *updateModel.state) + } _, err := updateQuery.Exec(ctx) return err } diff --git a/models/merge_request_test.go b/models/merge_request_test.go index cf5ae109..62a01449 100644 --- a/models/merge_request_test.go +++ b/models/merge_request_test.go @@ -25,12 +25,16 @@ func TestMergeRequestRepoInsert(t *testing.T) { t.Run("insert and get", func(t *testing.T) { mrModel := &models.MergeRequest{} require.NoError(t, gofakeit.Struct(mrModel)) + mrModel.MergeState = models.MergeStateInit newMrModel, err := mrRepo.Insert(ctx, mrModel) require.NoError(t, err) require.NotEqual(t, uuid.Nil, newMrModel.ID) getMRParams := models.NewGetMergeRequestParams(). - SetID(newMrModel.ID) + SetID(newMrModel.ID). + SetTargetBranch(newMrModel.TargetBranchID). + SetSourceBranch(newMrModel.SourceBranchID). + SetNumber(newMrModel.Sequence).SetState(models.MergeStateInit).SetTargetRepo(newMrModel.TargetRepoID) mrModel, err = mrRepo.Get(ctx, getMRParams) require.NoError(t, err) @@ -44,12 +48,14 @@ func TestMergeRequestRepoInsert(t *testing.T) { require.NoError(t, err) require.NotEqual(t, uuid.Nil, newMrModel.ID) - affectRows, err := mrRepo.Delete(ctx, models.NewDeleteMergeRequestParams().SetID(newMrModel.ID)) + deleteParams := models.NewDeleteMergeRequestParams().SetTargetRepo(newMrModel.TargetRepoID).SetNumber(newMrModel.Sequence) + affectRows, err := mrRepo.Delete(ctx, deleteParams) require.NoError(t, err) require.Equal(t, int64(1), affectRows) }) t.Run("list", func(t *testing.T) { + startT := time.Now() targetID := uuid.New() for i := 0; i < 10; i++ { mrModel := &models.MergeRequest{} @@ -68,8 +74,8 @@ func TestMergeRequestRepoInsert(t *testing.T) { require.True(t, hasMore) require.Len(t, mrs, 5) }) - t.Run("first page", func(t *testing.T) { - mrs, hasMore, err := mrRepo.List(ctx, models.NewListMergeRequestParams().SetTargetRepoID(targetID).SetAmount(5).SetAfter(time.Now().Add(time.Second*3))) + t.Run("last page", func(t *testing.T) { + mrs, hasMore, err := mrRepo.List(ctx, models.NewListMergeRequestParams().SetTargetRepoID(targetID).SetAmount(5).SetAfter(startT)) require.NoError(t, err) require.False(t, hasMore) require.Len(t, mrs, 0) @@ -86,7 +92,10 @@ func TestMergeRequestRepoInsert(t *testing.T) { newMrModel.Title = "Merge: xxxxx" newMrModel.Description = utils.String("vvvv") - updateMrParams := models.NewUpdateMergeRequestParams(newMrModel.ID).SetTitle("Merge: xxxx").SetDescription("test update") + updateMrParams := models.NewUpdateMergeRequestParams(newMrModel.TargetRepoID, newMrModel.Sequence). + SetTitle("Merge: xxxx"). + SetDescription("test update"). + SetState(models.MergeStateClosed) err = mrRepo.UpdateByID(ctx, updateMrParams) require.NoError(t, err) @@ -98,5 +107,7 @@ func TestMergeRequestRepoInsert(t *testing.T) { require.Equal(t, "Merge: xxxx", mrModel.Title) require.Equal(t, "test update", *mrModel.Description) + + require.Equal(t, models.MergeStateClosed, mrModel.MergeState) }) } diff --git a/utils/convert_types.go b/utils/convert_types.go index cf8c4281..cbc8f153 100644 --- a/utils/convert_types.go +++ b/utils/convert_types.go @@ -916,3 +916,11 @@ func TimeValueMap(src map[string]*time.Time) map[string]time.Time { } return dst } + +// Map get value of map ptr, if ptr is nil, make empty map +func Map[T any](src *map[string]T) map[string]T { + if src == nil || *src == nil { + return make(map[string]T) + } + return *src +} diff --git a/utils/convert_types_test.go b/utils/convert_types_test.go index 95308850..4f27ce39 100644 --- a/utils/convert_types_test.go +++ b/utils/convert_types_test.go @@ -1,6 +1,8 @@ package utils import ( + "github.com/stretchr/testify/require" + "reflect" "testing" "time" @@ -1530,3 +1532,16 @@ func TestMillisecondsTimeValue(t *testing.T) { } } } + +func TestMap(t *testing.T) { + t.Run("nil", func(t *testing.T) { + var v map[string]string + result := Map[string](&v) + require.NotNil(t, result) + }) + t.Run("nil", func(t *testing.T) { + v := make(map[string]string) + result := Map[string](&v) + require.Equal(t, v, result) + }) +} diff --git a/versionmgr/changes.go b/versionmgr/changes.go index 319ed614..94e6cfdd 100644 --- a/versionmgr/changes.go +++ b/versionmgr/changes.go @@ -131,6 +131,13 @@ type ChangePair struct { IsConflict bool } +func (changePair ChangePair) Path() string { + if changePair.Left != nil { + return changePair.Left.Path() + } + return changePair.Right.Path() +} + type ChangesPairIter struct { leftChanges *Changes rightChanges *Changes diff --git a/versionmgr/work_repo.go b/versionmgr/work_repo.go index 2ef4c501..ca51b101 100644 --- a/versionmgr/work_repo.go +++ b/versionmgr/work_repo.go @@ -737,6 +737,7 @@ func findBestAncestor(ctx context.Context, if baseCommit == nil && toMergeCommit != nil { return toMergeCommit, nil } + if baseCommit != nil && toMergeCommit == nil { return baseCommit, nil } diff --git a/versionmgr/work_repo_test.go b/versionmgr/work_repo_test.go index 1d9861c2..0d6fd30b 100644 --- a/versionmgr/work_repo_test.go +++ b/versionmgr/work_repo_test.go @@ -353,6 +353,187 @@ func TestWorkRepositoryRevert(t *testing.T) { }) } +func TestWorkRepositoryMergeState(t *testing.T) { + ctx := context.Background() + postgres, _, db := testhelper.SetupDatabase(ctx, t) + defer postgres.Stop() //nolint + repo := models.NewRepo(db) + + user, err := makeUser(ctx, repo.UserRepo(), "admin") + require.NoError(t, err) + + t.Run("not on branch", func(t *testing.T) { + adapter := mem.New(ctx) + + project, err := makeRepository(ctx, repo, user, t.Name()) + require.NoError(t, err) + testData1 := ` +1|a.txt |a +` + workRepo := NewWorkRepositoryFromAdapter(ctx, user, project, repo, adapter) + + //base branch + err = workRepo.CheckOut(ctx, InCommit, hash.Empty.Hex()) + require.NoError(t, err) + _, err = workRepo.CreateBranch(ctx, "feat/base") + require.NoError(t, err) + baseCommit, err := addChangesToWip(ctx, workRepo, "feat/base", "base commit", testData1) + require.NoError(t, err) + + _, err = workRepo.GetMergeState(ctx, baseCommit.Hash) + require.Error(t, err) + }) + + t.Run("base is nil", func(t *testing.T) { + adapter := mem.New(ctx) + + project, err := makeRepository(ctx, repo, user, t.Name()) + require.NoError(t, err) + testData1 := ` +1|a.txt |a +` + workRepo := NewWorkRepositoryFromAdapter(ctx, user, project, repo, adapter) + + //base branch + err = workRepo.CheckOut(ctx, InCommit, hash.Empty.Hex()) + require.NoError(t, err) + _, err = workRepo.CreateBranch(ctx, "feat/base") + require.NoError(t, err) + baseCommit, err := addChangesToWip(ctx, workRepo, "feat/base", "base commit", testData1) + require.NoError(t, err) + + err = workRepo.CheckOut(ctx, InBranch, "main") + require.NoError(t, err) + changes, err := workRepo.GetMergeState(ctx, baseCommit.Hash) + require.NoError(t, err) + require.Len(t, changes, 1) + }) + t.Run("toMerge is nil", func(t *testing.T) { + adapter := mem.New(ctx) + + project, err := makeRepository(ctx, repo, user, t.Name()) + require.NoError(t, err) + testData1 := ` +1|a.txt |a +` + workRepo := NewWorkRepositoryFromAdapter(ctx, user, project, repo, adapter) + + //base branch + err = workRepo.CheckOut(ctx, InCommit, hash.Empty.Hex()) + require.NoError(t, err) + _, err = workRepo.CreateBranch(ctx, "feat/base") + require.NoError(t, err) + _, err = addChangesToWip(ctx, workRepo, "feat/base", "base commit", testData1) + require.NoError(t, err) + + err = workRepo.CheckOut(ctx, InBranch, "feat/base") + require.NoError(t, err) + changes, err := workRepo.GetMergeState(ctx, hash.Empty) + require.NoError(t, err) + require.Len(t, changes, 1) + }) + + t.Run("both is nil", func(t *testing.T) { + adapter := mem.New(ctx) + + project, err := makeRepository(ctx, repo, user, t.Name()) + require.NoError(t, err) + + workRepo := NewWorkRepositoryFromAdapter(ctx, user, project, repo, adapter) + + err = workRepo.CheckOut(ctx, InBranch, "main") + require.NoError(t, err) + _, err = workRepo.GetMergeState(ctx, hash.Empty) + require.Error(t, err) + }) + + t.Run("no common root", func(t *testing.T) { + adapter := mem.New(ctx) + + project, err := makeRepository(ctx, repo, user, t.Name()) + require.NoError(t, err) + //commit1 a.txt b/c.txt b/e.txt + //commit2 a.txt b/d.txt b/e.txt + testData1 := ` +1|a.txt |a +` + workRepo := NewWorkRepositoryFromAdapter(ctx, user, project, repo, adapter) + + //base branch + err = workRepo.CheckOut(ctx, InCommit, hash.Empty.Hex()) + require.NoError(t, err) + _, err = workRepo.CreateBranch(ctx, "feat/base") + require.NoError(t, err) + _, err = addChangesToWip(ctx, workRepo, "feat/base", "base commit", testData1) + require.NoError(t, err) + + testData2 := ` +1|b/g.txt |g1 +` + + err = workRepo.CheckOut(ctx, InBranch, "main") + require.NoError(t, err) + _, err = workRepo.CreateBranch(ctx, "feat/diff") + require.NoError(t, err) + + secondCommit, err := addChangesToWip(ctx, workRepo, "feat/diff", "merge commit", testData2) + require.NoError(t, err) + + err = workRepo.CheckOut(ctx, InBranch, "feat/base") + require.NoError(t, err) + _, err = workRepo.GetMergeState(ctx, secondCommit.Hash) + require.Error(t, err) + }) + + t.Run("get merge state", func(t *testing.T) { + adapter := mem.New(ctx) + + project, err := makeRepository(ctx, repo, user, t.Name()) + require.NoError(t, err) + //commit1 a.txt b/c.txt b/e.txt + //commit2 a.txt b/d.txt b/e.txt + + workRepo := NewWorkRepositoryFromAdapter(ctx, user, project, repo, adapter) + err = workRepo.CheckOut(ctx, InBranch, "main") + require.NoError(t, err) + _, err = workRepo.CreateBranch(ctx, "feat/init") + require.NoError(t, err) + testData := ` +1|readme.md |a +` + _, err = addChangesToWip(ctx, workRepo, "main", "init commit", testData) + require.NoError(t, err) + + testData1 := ` +1|a.txt |a +` + //base branch + err = workRepo.CheckOut(ctx, InBranch, "main") + require.NoError(t, err) + _, err = workRepo.CreateBranch(ctx, "feat/base") + require.NoError(t, err) + _, err = addChangesToWip(ctx, workRepo, "feat/base", "base commit", testData1) + require.NoError(t, err) + + testData2 := ` +1|b/g.txt |g1 +` + + err = workRepo.CheckOut(ctx, InBranch, "main") + require.NoError(t, err) + _, err = workRepo.CreateBranch(ctx, "feat/diff") + require.NoError(t, err) + secondCommit, err := addChangesToWip(ctx, workRepo, "feat/diff", "merge commit", testData2) + require.NoError(t, err) + + err = workRepo.CheckOut(ctx, InBranch, "feat/base") + require.NoError(t, err) + changes, err := workRepo.GetMergeState(ctx, secondCommit.Hash) + require.NoError(t, err) + require.Len(t, changes, 2) + }) +} + func makeUser(ctx context.Context, userRepo models.IUserRepo, name string) (*models.User, error) { user := &models.User{ Name: name, From 3632e632dc855251d71476f7f55faba9c0337154 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Tue, 9 Jan 2024 17:02:52 +0800 Subject: [PATCH 140/210] feat: rm temp data --- "\\" | 23 ---- api/jiaozifs.gen.go | 6 +- api/swagger.yml | 2 +- controller/wip_ctl.go | 4 +- integrationtest/helper_test.go | 23 ++-- integrationtest/merge_request_test.go | 9 +- integrationtest/wip_object_test.go | 46 +++++++ models/branch_test.go | 4 +- models/commit_test.go | 8 +- models/merge_request_test.go | 4 +- models/repo_test.go | 4 +- models/repository_test.go | 8 +- models/tag_test.go | 8 +- models/tree_test.go | 8 +- models/user.go | 28 ++-- models/user_test.go | 30 ++++- models/wip_test.go | 26 +++- testhelper/pg.go | 9 +- utils/hash/hash_test.go | 5 + versionmgr/changes.go | 86 ++++++------ versionmgr/changes_test.go | 87 ++++++------ versionmgr/commit_node_test.go | 4 +- versionmgr/commit_walker_bfs_filtered_test.go | 4 +- versionmgr/commit_walker_bfs_test.go | 4 +- versionmgr/commit_walker_ctime_test.go | 4 +- versionmgr/commit_walker_test.go | 8 +- versionmgr/conflict.go | 80 ++++------- versionmgr/conflict_test.go | 126 ++++++++++++++++++ versionmgr/merge_base_test.go | 4 +- versionmgr/work_repo_diff_test.go | 4 +- versionmgr/work_repo_merge_test.go | 8 +- versionmgr/work_repo_test.go | 20 +-- versionmgr/worktree_test.go | 10 +- 33 files changed, 446 insertions(+), 258 deletions(-) delete mode 100644 "\\" create mode 100644 versionmgr/conflict_test.go diff --git "a/\\" "b/\\" deleted file mode 100644 index 1a71fdce..00000000 --- "a/\\" +++ /dev/null @@ -1,23 +0,0 @@ -feat: add models and api stubs - -# Please enter the commit message for your changes. Lines starting -# with '#' will be ignored, and an empty message aborts the commit. -# -# interactive rebase in progress; onto 571ab23 -# Last command done (1 command done): -# pick 541e6f6 feat: add models and api stubs -# Next commands to do (3 remaining commands): -# pick 2d7335b fix -# pick ef83382 feat: impl mergerequest api -# You are currently rebasing branch 'feat/add_mergerequest_api' on '571ab23'. -# -# Changes to be committed: -# modified: api/api_impl/impl.go -# modified: api/jiaozifs.gen.go -# modified: api/swagger.yml -# new file: controller/merge_request_ctl.go -# modified: models/merge_request.go -# modified: versionmgr/changes.go -# new file: versionmgr/conflict.go -# modified: versionmgr/work_repo.go -# diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index c0939a1e..5b1ec984 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -180,7 +180,7 @@ type LoginConfigRBAC string // MergeMergeRequest defines model for MergeMergeRequest. type MergeMergeRequest struct { - // ConflictResolve use to record the resolution of the conflict, example({"b/a.txt":"base"}) + // ConflictResolve use to record the resolution of the conflict, example({"b/a.txt":"left"}) ConflictResolve *map[string]string `json:"conflict_resolve,omitempty"` Msg string `json:"msg"` } @@ -8326,7 +8326,7 @@ var swaggerSpec = []string{ "R69q5RkphRi8y5jhIkxiCMeVnaw+oH2hhgmJSGO8sJPhAs1nDKnv1RPd2y8IoyiLYySASqABGCeNCMSB", "hsAhvKSEovcXH08QpiFK8ELBjFSahFFM6DftwqGSl7pblICcsfCSdnPNKZKUk6QikEESYJl0d9buZEro", "FLFM7i5dsyWNTinXBnatVL3f9W96uU025iBYfK0licOQKOpxfFp3rnvx21NT1LGkgPEQyZnyowWLM/Ua", - "sUg/yYfzEdzgJI3hxe2lNxnhXXkjL73DS21vX3p3Lz3HdBKhAQfHMZsfJ6lc/K5jDoeSZ7CMlerbThZ1", + "sUg/yYfzEdzgJI3hxe2lNxnhXXkjL73DS22wXnp3Lz3HdBKhAQfHMZsfJ6lc/K5jDoeSZ7CMlerbThZ1", "csdYKkOtqXU2lA35y8Z6EhLLTDR3FOd+ItSUaVDffrLu/admVwwiyX6hjL/B5mjVolnli5UGyU2tLUUK", "Cs4259NkYotFrenkxDbk61f0crWtu6rtyjo6l1jCvdVe+33DvfyKQ+vYXn4soh+LaEuLKFfUrSynx42c", "1baxjcXPftP/UyAhHJbDDIJvQlncrkgzU4acHJsXTZvI9IsSCAlGuolzNUocYomXTd109lkA/5h/ob7W", @@ -8385,7 +8385,7 @@ var swaggerSpec = []string{ "bu0G7MdBwPvFCGJ11H0CLuOcpDVfMeVMH/hSKtcIMHwnoMvhGvhA0P03MJj99q3hEJEbxCK0xOKxAZ+1", "EP1MC6Fm963k5hohPhoEOoazMb0ixucK7lmqK4V8bUzwEShDVDM//wFe/RWO4zY+1gMWt9Vbrr9eKdlW", "b9w2T2q3an+9UjIyqO3aUCt5OwPsNEyZudewvML6cDSKWYDjGRPy8NXrv++/GuGUjK73vbZ2Le2w+PTq", - "7v8DAAD//yVnSbY2hwAA", + "7v8DAAD//9cankg2hwAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 5e762c12..bcc3eada 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -176,7 +176,7 @@ components: type: string allowEmptyValue: true conflict_resolve: - description: use to record the resolution of the conflict, example({"b/a.txt":"base"}) + description: use to record the resolution of the conflict, example({"b/a.txt":"left"}) type: object additionalProperties: type: string diff --git a/controller/wip_ctl.go b/controller/wip_ctl.go index 5f38c992..1bb5a4e2 100644 --- a/controller/wip_ctl.go +++ b/controller/wip_ctl.go @@ -189,7 +189,7 @@ func (wipCtl WipController) UpdateWip(ctx context.Context, w *api.JiaozifsRespon updateParams := models.NewUpdateWipParams(wip.ID) if body.BaseCommit != nil { - baseCommitHash, err := hash.FromHex(*body.BaseCommit) + baseCommitHash, err := hash.FromHex(utils.StringValue(body.BaseCommit)) if err != nil { w.Error(err) return @@ -205,7 +205,7 @@ func (wipCtl WipController) UpdateWip(ctx context.Context, w *api.JiaozifsRespon updateParams.SetBaseCommit(baseCommitHash) } if body.CurrentTree != nil { - currentTreeHash, err := hash.FromHex(*body.CurrentTree) + currentTreeHash, err := hash.FromHex(utils.StringValue(body.CurrentTree)) if err != nil { w.Error(err) return diff --git a/integrationtest/helper_test.go b/integrationtest/helper_test.go index 600e0748..b3ca06ad 100644 --- a/integrationtest/helper_test.go +++ b/integrationtest/helper_test.go @@ -13,17 +13,14 @@ import ( "testing" "time" - "github.com/jiaozifs/jiaozifs/utils" - "github.com/jiaozifs/jiaozifs/api" - "github.com/smartystreets/goconvey/convey" - - "github.com/jiaozifs/jiaozifs/testhelper" - - "github.com/stretchr/testify/require" - "github.com/jiaozifs/jiaozifs/cmd" + "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/jiaozifs/jiaozifs/utils" + openapi_types "github.com/oapi-codegen/runtime/types" "github.com/phayes/freeport" + "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/require" ) func InitCmd(ctx context.Context, jzHome string, listen string, db string) error { @@ -57,7 +54,7 @@ func TestDoubleInit(t *testing.T) { //nolint type Closer func() func SetupDaemon(t *testing.T, ctx context.Context) (string, Closer) { //nolint - pg, connectString, _ := testhelper.SetupDatabase(ctx, t) + closeDB, connectString, _ := testhelper.SetupDatabase(ctx, t) port, err := freeport.GetFreePort() require.NoError(t, err) @@ -70,7 +67,8 @@ func SetupDaemon(t *testing.T, ctx context.Context) (string, Closer) { //nolint closer := func() { cancel() - require.NoError(t, pg.Stop()) + require.NoError(t, os.RemoveAll(tmpDir)) + closeDB() } go func() { err := Daemon(ctx, buf, tmpDir, url) @@ -104,12 +102,15 @@ func SetupDaemon(t *testing.T, ctx context.Context) (string, Closer) { //nolint } } +var count int + func createUser(ctx context.Context, c convey.C, client *api.Client, userName string) { c.Convey("register "+userName, func() { + count++ resp, err := client.Register(ctx, api.RegisterJSONRequestBody{ Name: userName, Password: "12345678", - Email: "mock@gmail.com", + Email: openapi_types.Email(fmt.Sprintf("mock%d@gmail.com", count)), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) diff --git a/integrationtest/merge_request_test.go b/integrationtest/merge_request_test.go index 578b9bfd..04554373 100644 --- a/integrationtest/merge_request_test.go +++ b/integrationtest/merge_request_test.go @@ -337,15 +337,14 @@ func MergeRequestSpec(ctx context.Context, urlStr string) func(c convey.C) { result, err := api.ParseListMergeRequestsResponse(resp) convey.So(err, convey.ShouldBeNil) convey.ShouldHaveLength(*result.JSON200, 2) - if i > 5 { + if i >= 5 { convey.ShouldBeFalse((*result.JSON200).Pagination.HasMore) } else { convey.ShouldBeTrue((*result.JSON200).Pagination.HasMore) + next, err := time.Parse(`2006-01-02 15:04:05.999999999 -0700 MST`, (*result.JSON200).Pagination.NextOffset) + convey.So(err, convey.ShouldBeNil) + after = &next } - fmt.Println((*result.JSON200).Pagination.NextOffset) - next, err := time.Parse(`2006-01-02 15:04:05.000000 +0800 CST`, (*result.JSON200).Pagination.NextOffset) - convey.So(err, convey.ShouldBeNil) - after = &next } }) }) diff --git a/integrationtest/wip_object_test.go b/integrationtest/wip_object_test.go index ee20dd1c..efe7b104 100644 --- a/integrationtest/wip_object_test.go +++ b/integrationtest/wip_object_test.go @@ -471,8 +471,14 @@ func UpdateWipSpec(ctx context.Context, urlStr string) func(c convey.C) { loginAndSwitch(ctx, c, client, "jude login", userName, false) createRepo(ctx, c, client, repoName) createWip(ctx, c, client, "get wip obj test", userName, repoName, branchName) + + //make wip base commit has value + uploadObject(ctx, c, client, "update init object", userName, repoName, branchName, "a.txt") + commitWip(ctx, c, client, "commit init object", userName, repoName, branchName, "test") + uploadObject(ctx, c, client, "update f1 to test branch", userName, repoName, branchName, "m.dat") uploadObject(ctx, c, client, "update f2 to test branch", userName, repoName, branchName, "g/m.dat") + c.Convey("get wip", func(c convey.C) { resp, err := client.GetWip(ctx, userName, repoName, &api.GetWipParams{ RefName: branchName, @@ -535,6 +541,26 @@ func UpdateWipSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) }) + c.Convey("update wip with fail base commit", func() { + //delete + resp, err := client.UpdateWip(ctx, userName, repoName, &api.UpdateWipParams{RefName: branchName}, api.UpdateWipJSONRequestBody{ + CurrentTree: utils.String(hash.Empty.Hex()), + BaseCommit: utils.String("ddd"), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusInternalServerError) + }) + + c.Convey("update wip with fail tree hash", func() { + //delete + resp, err := client.UpdateWip(ctx, userName, repoName, &api.UpdateWipParams{RefName: branchName}, api.UpdateWipJSONRequestBody{ + CurrentTree: utils.String("ddd"), + BaseCommit: utils.String(hash.Empty.Hex()), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusInternalServerError) + }) + c.Convey("update wip successful", func() { //delete resp, err := client.UpdateWip(ctx, userName, repoName, &api.UpdateWipParams{RefName: branchName}, api.UpdateWipJSONRequestBody{ @@ -555,6 +581,26 @@ func UpdateWipSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So((*updatedWip.JSON200).CurrentTree, convey.ShouldEqual, "") }) + c.Convey("fail to update non exit tree hash", func() { + //delete + resp, err := client.UpdateWip(ctx, userName, repoName, &api.UpdateWipParams{RefName: branchName}, api.UpdateWipJSONRequestBody{ + CurrentTree: utils.String("6161616161"), + BaseCommit: utils.String(wip.BaseCommit), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to update non exit base commit", func() { + //delete + resp, err := client.UpdateWip(ctx, userName, repoName, &api.UpdateWipParams{RefName: branchName}, api.UpdateWipJSONRequestBody{ + CurrentTree: utils.String(wip.CurrentTree), + BaseCommit: utils.String("6161616161"), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + c.Convey("update wip to non empty successful", func() { //delete resp, err := client.UpdateWip(ctx, userName, repoName, &api.UpdateWipParams{RefName: branchName}, api.UpdateWipJSONRequestBody{ diff --git a/models/branch_test.go b/models/branch_test.go index 2c938ca2..0c09fe15 100644 --- a/models/branch_test.go +++ b/models/branch_test.go @@ -15,8 +15,8 @@ import ( func TestRefRepoInsert(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() repo := models.NewBranchRepo(db) diff --git a/models/commit_test.go b/models/commit_test.go index ee36d139..d1fc1bae 100644 --- a/models/commit_test.go +++ b/models/commit_test.go @@ -14,8 +14,8 @@ import ( func TestCommitRepo(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() repoID := uuid.New() commitRepo := models.NewCommitRepo(db, repoID) @@ -41,8 +41,8 @@ func TestCommitRepo(t *testing.T) { func TestDeleteCommit(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() t.Run("delete commit", func(t *testing.T) { repoID := uuid.New() commitRepo := models.NewCommitRepo(db, repoID) diff --git a/models/merge_request_test.go b/models/merge_request_test.go index 62a01449..2e0e498f 100644 --- a/models/merge_request_test.go +++ b/models/merge_request_test.go @@ -17,8 +17,8 @@ import ( func TestMergeRequestRepoInsert(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() mrRepo := models.NewMergeRequestRepo(db) diff --git a/models/repo_test.go b/models/repo_test.go index 57cbf800..aee53d2e 100644 --- a/models/repo_test.go +++ b/models/repo_test.go @@ -16,8 +16,8 @@ import ( func TestRepoTransaction(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() t.Run("simple", func(t *testing.T) { pgRepo := models.NewRepo(db) diff --git a/models/repository_test.go b/models/repository_test.go index 33538e30..0b0e12cb 100644 --- a/models/repository_test.go +++ b/models/repository_test.go @@ -15,8 +15,8 @@ import ( func TestRepositoryUpdate(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() repo := models.NewRepositoryRepo(db) @@ -49,8 +49,8 @@ func TestRepositoryUpdate(t *testing.T) { func TestRepositoryRepoInsert(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() repo := models.NewRepositoryRepo(db) diff --git a/models/tag_test.go b/models/tag_test.go index f3744521..b18938f3 100644 --- a/models/tag_test.go +++ b/models/tag_test.go @@ -14,8 +14,8 @@ import ( func TestTagRepo(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() repoID := uuid.New() tagRepo := models.NewTagRepo(db, repoID) @@ -41,8 +41,8 @@ func TestTagRepo(t *testing.T) { func TestDeleteTag(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() t.Run("delete tag", func(t *testing.T) { repoID := uuid.New() tagRepo := models.NewTagRepo(db, repoID) diff --git a/models/tree_test.go b/models/tree_test.go index ecc74139..cd5d7d9a 100644 --- a/models/tree_test.go +++ b/models/tree_test.go @@ -39,8 +39,8 @@ func Test_sortSubObjects(t *testing.T) { func TestObjectRepo_Insert(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() repoID := uuid.New() repo := models.NewFileTree(db, repoID) @@ -99,8 +99,8 @@ func TestNewTreeNode(t *testing.T) { func TestFileTreeRepo_Delete(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() repoID := uuid.New() repo := models.NewFileTree(db, repoID) diff --git a/models/user.go b/models/user.go index 8ee40aeb..b51109fa 100644 --- a/models/user.go +++ b/models/user.go @@ -11,8 +11,8 @@ import ( type User struct { bun.BaseModel `bun:"table:users"` ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()" json:"id"` - Name string `bun:"name,notnull" json:"name"` - Email string `bun:"email,notnull" json:"email"` + Name string `bun:"name,unique,notnull" json:"name"` + Email string `bun:"email,unique,notnull" json:"email"` EncryptedPassword string `bun:"encrypted_password,notnull" json:"encrypted_password"` CurrentSignInAt time.Time `bun:"current_sign_in_at" json:"current_sign_in_at"` LastSignInAt time.Time `bun:"last_sign_in_at" json:"last_sign_in_at"` @@ -47,7 +47,23 @@ func (gup *GetUserParams) SetEmail(email string) *GetUserParams { return gup } -type CountUserParams = GetUserParams +type CountUserParams struct { + name *string + email *string +} + +func NewCountUserParams() *CountUserParams { + return &CountUserParams{} +} + +func (gup *CountUserParams) SetName(name string) *CountUserParams { + gup.name = &name + return gup +} +func (gup *CountUserParams) SetEmail(email string) *CountUserParams { + gup.email = &email + return gup +} func NewCountUserParam() *CountUserParams { return &CountUserParams{} @@ -93,13 +109,9 @@ func (userRepo *UserRepo) Get(ctx context.Context, params *GetUserParams) (*User return user, nil } -func (userRepo *UserRepo) Count(ctx context.Context, params *GetUserParams) (int, error) { +func (userRepo *UserRepo) Count(ctx context.Context, params *CountUserParams) (int, error) { query := userRepo.db.NewSelect().Model((*User)(nil)) - if uuid.Nil != params.id { - query = query.Where("id = ?", params.id) - } - if params.name != nil { query = query.Where("name = ?", params.name) } diff --git a/models/user_test.go b/models/user_test.go index 335bc2ad..95c2201e 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -19,8 +19,8 @@ var dbTimeCmpOpt = cmp.Comparer(func(x, y time.Time) bool { func TestNewUserRepo(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() repo := models.NewUserRepo(db) @@ -47,3 +47,29 @@ func TestNewUserRepo(t *testing.T) { require.NoError(t, err) require.True(t, cmp.Equal(userModel, userByName, dbTimeCmpOpt)) } + +func TestCount(t *testing.T) { + ctx := context.Background() + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() + + repo := models.NewUserRepo(db) + + var users []*models.User + for i := 0; i < 5; i++ { + userModel := &models.User{} + require.NoError(t, gofakeit.Struct(userModel)) + newUser, err := repo.Insert(ctx, userModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newUser.ID) + users = append(users, newUser) + } + + count, err := repo.Count(ctx, models.NewCountUserParams().SetName(users[0].Name)) + require.NoError(t, err) + require.Equal(t, 1, count) + + count, err = repo.Count(ctx, models.NewCountUserParams().SetEmail(users[0].Email)) + require.NoError(t, err) + require.Equal(t, 1, count) +} diff --git a/models/wip_test.go b/models/wip_test.go index 49ff6cd8..0640ed56 100644 --- a/models/wip_test.go +++ b/models/wip_test.go @@ -15,8 +15,8 @@ import ( func TestWipRepo(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() repo := models.NewWipRepo(db) @@ -44,6 +44,14 @@ func TestWipRepo(t *testing.T) { require.NoError(t, err) require.NotEqual(t, uuid.Nil, secNewWipModel.ID) + thirdWipModel := &models.WorkingInProcess{} + require.NoError(t, gofakeit.Struct(thirdWipModel)) + thirdWipModel.CreatorID = uuid.New() + thirdWipModel.RepositoryID = newWipModel.RepositoryID + thirdWipModel.RefID = newWipModel.RefID + _, err = repo.Insert(ctx, thirdWipModel) + require.NoError(t, err) + listParams := models.NewListWipParams(). SetCreatorID(secNewWipModel.CreatorID). SetRepositoryID(secNewWipModel.RepositoryID) @@ -52,6 +60,16 @@ func TestWipRepo(t *testing.T) { require.NoError(t, err) require.Len(t, list, 2) + { + listParams := models.NewListWipParams(). + SetRepositoryID(newWipModel.RepositoryID). + SetRefID(newWipModel.RefID) + + list, err := repo.List(ctx, listParams) + require.NoError(t, err) + require.Len(t, list, 2) + } + deleteParams := models.NewDeleteWipParams(). SetID(secWipModel.ID). SetCreatorID(secWipModel.CreatorID). @@ -72,8 +90,8 @@ func TestWipRepo(t *testing.T) { func TestWipRepoUpdateByID(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() repo := models.NewWipRepo(db) diff --git a/testhelper/pg.go b/testhelper/pg.go index f50fc2ac..d86d7eb3 100644 --- a/testhelper/pg.go +++ b/testhelper/pg.go @@ -18,7 +18,9 @@ import ( var TestConnTmpl = "postgres://postgres:postgres@localhost:%d/jiaozifs?sslmode=disable" -func SetupDatabase(ctx context.Context, t *testing.T) (*embeddedpostgres.EmbeddedPostgres, string, *bun.DB) { +type CloseFunc func() + +func SetupDatabase(ctx context.Context, t *testing.T) (CloseFunc, string, *bun.DB) { port, err := freeport.GetFreePort() require.NoError(t, err) tmpDir, err := os.MkdirTemp(os.TempDir(), "*") @@ -39,5 +41,8 @@ func SetupDatabase(ctx context.Context, t *testing.T) (*embeddedpostgres.Embedde err = migrations.MigrateDatabase(ctx, db) require.NoError(t, err) - return postgres, connStr, db + return func() { + require.NoError(t, postgres.Stop()) + require.NoError(t, os.RemoveAll(tmpDir)) + }, connStr, db } diff --git a/utils/hash/hash_test.go b/utils/hash/hash_test.go index 782b85b2..3958c0ff 100644 --- a/utils/hash/hash_test.go +++ b/utils/hash/hash_test.go @@ -46,6 +46,11 @@ func TestHexArrayOfHashes(t *testing.T) { } func TestHashFromHex(t *testing.T) { + t.Run("hex nil", func(t *testing.T) { + require.Equal(t, "", (Hash(nil)).Hex()) + require.Equal(t, "616161", (Hash("aaa")).Hex()) + }) + t.Run("empty", func(t *testing.T) { hash, err := FromHex("") require.NoError(t, err) diff --git a/versionmgr/changes.go b/versionmgr/changes.go index 94e6cfdd..d60c425e 100644 --- a/versionmgr/changes.go +++ b/versionmgr/changes.go @@ -143,8 +143,8 @@ type ChangesPairIter struct { rightChanges *Changes } -func NewChangesPairIter(baseChanges *Changes, mergerChanges *Changes) *ChangesPairIter { - return &ChangesPairIter{leftChanges: baseChanges, rightChanges: mergerChanges} +func NewChangesPairIter(leftChanges *Changes, rightChanges *Changes) *ChangesPairIter { + return &ChangesPairIter{leftChanges: leftChanges, rightChanges: rightChanges} } // Has check if any changes @@ -160,97 +160,97 @@ func (cw *ChangesPairIter) Reset() { // Next find change file, first sort each file in change, pop files from two changes, compare filename, // -// base file < merge file, pop base change and put merge file back to queue -// base file > merge file, pop merge file and put base file back to queue +// left file < right file, pop left change and put right file back to queue +// left file > right file, pop right file and put left file back to queue // both file name match, try to resolve file changes // if one of the queue consume up, pick left change in the other queue func (cw *ChangesPairIter) Next() (*ChangePair, error) { - baseNode, baseErr := cw.leftChanges.Next() - if baseErr != nil && baseErr != io.EOF { - return nil, baseErr + leftNode, leftErr := cw.leftChanges.Next() + if leftErr != nil && leftErr != io.EOF { + return nil, leftErr } - mergeNode, mergerError := cw.rightChanges.Next() - if mergerError != nil && mergerError != io.EOF { - return nil, mergerError + rightNode, rightError := cw.rightChanges.Next() + if rightError != nil && rightError != io.EOF { + return nil, rightError } - if baseErr == io.EOF && mergerError == io.EOF { + if leftErr == io.EOF && rightError == io.EOF { return nil, io.EOF } - if baseErr == io.EOF { - return &ChangePair{Right: mergeNode}, nil + if leftErr == io.EOF { + return &ChangePair{Right: rightNode}, nil } - if mergerError == io.EOF { - return &ChangePair{Left: baseNode}, nil + if rightError == io.EOF { + return &ChangePair{Left: leftNode}, nil } - compare := strings.Compare(baseNode.Path(), mergeNode.Path()) + compare := strings.Compare(leftNode.Path(), rightNode.Path()) if compare > 0 { - //only merger change + //only right change cw.leftChanges.Back() return &ChangePair{ - Right: mergeNode, + Right: rightNode, }, nil } else if compare == 0 { - isConflict, err := cw.isConflict(baseNode, mergeNode) + isConflict, err := cw.isConflict(leftNode, rightNode) if err != nil { return nil, err } return &ChangePair{ - Left: baseNode, - Right: mergeNode, + Left: leftNode, + Right: rightNode, IsConflict: isConflict, }, nil } - //only base change + //only left change cw.rightChanges.Back() return &ChangePair{ - Left: baseNode, + Left: leftNode, }, nil } -func (cw *ChangesPairIter) isConflict(base, merge IChange) (bool, error) { - baseAction, err := base.Action() +func (cw *ChangesPairIter) isConflict(left, right IChange) (bool, error) { + leftAction, err := left.Action() if err != nil { return false, err } - mergeAction, err := merge.Action() + rightAction, err := right.Action() if err != nil { return false, err } - switch baseAction { + switch leftAction { case merkletrie.Insert: - switch mergeAction { + switch rightAction { case merkletrie.Delete: - return false, fmt.Errorf("%s merge should never be Delete while the base diff is Insert, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues %w", base.Path(), ErrActionNotMatch) + return false, fmt.Errorf("%s right should never be Delete while the left diff is Insert, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues %w", left.Path(), ErrActionNotMatch) case merkletrie.Modify: - return false, fmt.Errorf("%s merge should never be Modify while the base diff is Insert, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues %w", base.Path(), ErrActionNotMatch) + return false, fmt.Errorf("%s right should never be Modify while the left diff is Insert, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues %w", left.Path(), ErrActionNotMatch) case merkletrie.Insert: - if bytes.Equal(base.To().Hash(), merge.To().Hash()) { + if bytes.Equal(left.To().Hash(), right.To().Hash()) { return false, nil } return true, nil } case merkletrie.Delete: - switch mergeAction { + switch rightAction { case merkletrie.Delete: return false, nil case merkletrie.Insert: - return false, fmt.Errorf("%s merge should never be Insert while the other diff is Delete, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues %w", base.Path(), ErrActionNotMatch) + return false, fmt.Errorf("%s right should never be Insert while the other diff is Delete, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues %w", left.Path(), ErrActionNotMatch) case merkletrie.Modify: return true, nil } case merkletrie.Modify: - switch mergeAction { + switch rightAction { case merkletrie.Insert: - return false, fmt.Errorf("%s merge should never be Insert while the other diff is Modify, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues %w", base.Path(), ErrActionNotMatch) + return false, fmt.Errorf("%s right should never be Insert while the other diff is Modify, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues %w", left.Path(), ErrActionNotMatch) case merkletrie.Delete: return true, nil case merkletrie.Modify: - if bytes.Equal(base.To().Hash(), merge.To().Hash()) { + if bytes.Equal(left.To().Hash(), right.To().Hash()) { return false, nil } return true, nil @@ -267,8 +267,8 @@ type ChangesMergeIter struct { } // NewChangesMergeIter create a changes iter with two changes set and resolver function -func NewChangesMergeIter(baseChanges *Changes, mergerChanges *Changes, resolver ConflictResolver) *ChangesMergeIter { - return &ChangesMergeIter{changePairIter: NewChangesPairIter(baseChanges, mergerChanges), resolver: resolver} +func NewChangesMergeIter(leftChanges *Changes, rightChanges *Changes, resolver ConflictResolver) *ChangesMergeIter { + return &ChangesMergeIter{changePairIter: NewChangesPairIter(leftChanges, rightChanges), resolver: resolver} } // Has check if any changes exit @@ -283,8 +283,8 @@ func (cw *ChangesMergeIter) Reset() { // Next find change file, first sort each file in change, pop files from two changes, compare filename, // -// base file < merge file, pop base change and put merge file back to queue -// base file > merge file, pop merge file and put base file back to queue +// left file < right file, pop left change and put right file back to queue +// left file > right file, pop right file and put left file back to queue // both file name match, try to resolve file changes // if one of the queue consume up, pick left change in the other queue func (cw *ChangesMergeIter) Next() (IChange, error) { @@ -302,13 +302,13 @@ func (cw *ChangesMergeIter) Next() (IChange, error) { return chPair.Right, nil } -func (cw *ChangesMergeIter) resolveConflict(base, merge IChange) (IChange, error) { +func (cw *ChangesMergeIter) resolveConflict(left, right IChange) (IChange, error) { if cw.resolver != nil { - resolveResult, err := cw.resolver(base, merge) + resolveResult, err := cw.resolver(left, right) if err != nil { return nil, err } return resolveResult, nil } - return nil, fmt.Errorf("path %s confilict %w", merge.Path(), ErrConflict) + return nil, fmt.Errorf("path %s confilict %w", right.Path(), ErrConflict) } diff --git a/versionmgr/changes_test.go b/versionmgr/changes_test.go index 5f5520e7..00a87852 100644 --- a/versionmgr/changes_test.go +++ b/versionmgr/changes_test.go @@ -92,42 +92,49 @@ func (m mockChange) String() string { panic("implement me") } -func makeMockChange(testData string) (*Changes, error) { +func makeMockChange(data string) (IChange, error) { + segs := strings.Split(data, "|") + num, err := strconv.Atoi(segs[0]) + if err != nil { + return nil, err + } + action := merkletrie.Action(num) + pHash := hash.Hash(strings.Trim(segs[2], " \t/")) + fullPath := filepath.Clean(strings.Trim(segs[1], " \t/")) + pathSeg := strings.Split(fullPath, "/") + path := make([]noder.Noder, len(pathSeg)) + for index, p := range pathSeg { + path[index] = NewMockNode(p, pHash) + } + c := &mockChange{ + action: action, + path: fullPath, + } + switch action { + case merkletrie.Delete: + c.from = path + c.to = nil + case merkletrie.Insert: + c.from = nil + c.to = path + case merkletrie.Modify: + c.from = nil + c.to = path + } + return c, nil +} +func makeMockChanges(testData string) (*Changes, error) { var changes []IChange for _, line := range strings.Split(testData, "\n") { line = strings.TrimSpace(line) if len(line) == 0 { continue } - segs := strings.Split(line, "|") - num, err := strconv.Atoi(segs[0]) + + c, err := makeMockChange(line) if err != nil { return nil, err } - action := merkletrie.Action(num) - pHash := hash.Hash(strings.Trim(segs[2], " \t/")) - fullPath := filepath.Clean(strings.Trim(segs[1], " \t/")) - pathSeg := strings.Split(fullPath, "/") - path := make([]noder.Noder, len(pathSeg)) - for index, p := range pathSeg { - path[index] = NewMockNode(p, pHash) - } - c := &mockChange{ - action: action, - path: fullPath, - } - switch action { - case merkletrie.Delete: - c.from = path - c.to = nil - case merkletrie.Insert: - c.from = nil - c.to = path - case merkletrie.Modify: - c.from = nil - c.to = path - } - changes = append(changes, c) } @@ -140,13 +147,13 @@ func TestNewChangesMergeIter(t *testing.T) { 1|a.txt|h1 1|b/a.txt|h2 ` - changeSet1, err := makeMockChange(changeData1) + changeSet1, err := makeMockChanges(changeData1) require.NoError(t, err) changeData2 := ` 1|c.txt|h1 1|d/a.txt|h2 ` - changeSet2, err := makeMockChange(changeData2) + changeSet2, err := makeMockChanges(changeData2) require.NoError(t, err) iter := NewChangesMergeIter(changeSet1, changeSet2, nil) @@ -169,14 +176,14 @@ func TestNewChangesMergeIter(t *testing.T) { 2|b/d.txt|h2 3|b/a.txt|h2 ` - changeSet1, err := makeMockChange(changeData1) + changeSet1, err := makeMockChanges(changeData1) require.NoError(t, err) changeData2 := ` 1|a.txt|h1 2|b/d.txt|h2 3|b/a.txt|h2 ` - changeSet2, err := makeMockChange(changeData2) + changeSet2, err := makeMockChanges(changeData2) require.NoError(t, err) iter := NewChangesMergeIter(changeSet1, changeSet2, nil) @@ -196,12 +203,12 @@ func TestNewChangesMergeIter(t *testing.T) { changeData1 := ` 1|a.txt|h1 ` - changeSet1, err := makeMockChange(changeData1) + changeSet1, err := makeMockChanges(changeData1) require.NoError(t, err) changeData2 := ` 1|a.txt|h2 ` - changeSet2, err := makeMockChange(changeData2) + changeSet2, err := makeMockChanges(changeData2) require.NoError(t, err) iter := NewChangesMergeIter(changeSet1, changeSet2, nil) @@ -215,12 +222,12 @@ func TestNewChangesMergeIter(t *testing.T) { changeData1 := ` 3|a.txt|h1 ` - changeSet1, err := makeMockChange(changeData1) + changeSet1, err := makeMockChanges(changeData1) require.NoError(t, err) changeData2 := ` 3|a.txt|h2 ` - changeSet2, err := makeMockChange(changeData2) + changeSet2, err := makeMockChanges(changeData2) require.NoError(t, err) iter := NewChangesMergeIter(changeSet1, changeSet2, nil) @@ -234,12 +241,12 @@ func TestNewChangesMergeIter(t *testing.T) { changeData1 := ` 2|a.txt|h1 ` - changeSet1, err := makeMockChange(changeData1) + changeSet1, err := makeMockChanges(changeData1) require.NoError(t, err) changeData2 := ` 2|a.txt|h2 ` - changeSet2, err := makeMockChange(changeData2) + changeSet2, err := makeMockChanges(changeData2) require.NoError(t, err) iter := NewChangesMergeIter(changeSet1, changeSet2, nil) @@ -258,13 +265,13 @@ func TestNewChangesMergeIter(t *testing.T) { 1|a.txt|h1 2|b.txt|h3 ` - changeSet1, err := makeMockChange(changeData1) + changeSet1, err := makeMockChanges(changeData1) require.NoError(t, err) changeData2 := ` 1|a.txt|h2 2|b.txt|h4 ` - changeSet2, err := makeMockChange(changeData2) + changeSet2, err := makeMockChanges(changeData2) require.NoError(t, err) iter := NewChangesMergeIter(changeSet1, changeSet2, LeastHashResolve) @@ -289,7 +296,7 @@ func TestChanges_ForEach(t *testing.T) { 1|a.txt|h1 1|b.txt|h2 ` - changeSet, err := makeMockChange(changeData1) + changeSet, err := makeMockChanges(changeData1) require.NoError(t, err) t.Run("simple", func(t *testing.T) { diff --git a/versionmgr/commit_node_test.go b/versionmgr/commit_node_test.go index ce96da65..6fc762f0 100644 --- a/versionmgr/commit_node_test.go +++ b/versionmgr/commit_node_test.go @@ -22,8 +22,8 @@ import ( // C---->D func TestWrapCommitNode(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() repoID := uuid.New() repo := models.NewRepo(db) diff --git a/versionmgr/commit_walker_bfs_filtered_test.go b/versionmgr/commit_walker_bfs_filtered_test.go index 22eca2ab..b807188c 100644 --- a/versionmgr/commit_walker_bfs_filtered_test.go +++ b/versionmgr/commit_walker_bfs_filtered_test.go @@ -15,8 +15,8 @@ import ( func TestNewFilterCommitIter(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() repoID := uuid.New() repo := models.NewRepo(db) diff --git a/versionmgr/commit_walker_bfs_test.go b/versionmgr/commit_walker_bfs_test.go index b68d1e79..4d9bfdc2 100644 --- a/versionmgr/commit_walker_bfs_test.go +++ b/versionmgr/commit_walker_bfs_test.go @@ -22,8 +22,8 @@ import ( // C---->D func TestBfsCommitIterator(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() repoID := uuid.New() repo := models.NewRepo(db) diff --git a/versionmgr/commit_walker_ctime_test.go b/versionmgr/commit_walker_ctime_test.go index a52362fe..a6b80fd4 100644 --- a/versionmgr/commit_walker_ctime_test.go +++ b/versionmgr/commit_walker_ctime_test.go @@ -25,8 +25,8 @@ import ( // use time.sleep to control the order, expect f-e-d-b-c-a-root func TestNewCommitIterCTime(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() repoID := uuid.New() repo := models.NewRepo(db) diff --git a/versionmgr/commit_walker_test.go b/versionmgr/commit_walker_test.go index dc50422b..58e8407a 100644 --- a/versionmgr/commit_walker_test.go +++ b/versionmgr/commit_walker_test.go @@ -22,8 +22,8 @@ import ( // C---->D func TestNewCommitPostorderIter(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() repoID := uuid.New() repo := models.NewRepo(db) @@ -125,8 +125,8 @@ func TestNewCommitPostorderIter(t *testing.T) { // C---->D func TestCommitPreorderIter(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() repoID := uuid.New() repo := models.NewRepo(db) diff --git a/versionmgr/conflict.go b/versionmgr/conflict.go index c1aac846..4194a69d 100644 --- a/versionmgr/conflict.go +++ b/versionmgr/conflict.go @@ -9,31 +9,31 @@ import ( ) // ConflictResolver resolve conflict between two change -type ConflictResolver func(base IChange, merged IChange) (IChange, error) +type ConflictResolver func(left IChange, right IChange) (IChange, error) -// LeastHashResolve use least hash change for test -func LeastHashResolve(base IChange, merged IChange) (IChange, error) { - baseAction, err := base.Action() +// LeastHashResolve use the least hash change for test +func LeastHashResolve(left IChange, right IChange) (IChange, error) { + leftAction, err := left.Action() if err != nil { return nil, err } - mergeAction, err := merged.Action() + rightAction, err := right.Action() if err != nil { return nil, err } - if baseAction == merkletrie.Delete { - return merged, nil + if leftAction == merkletrie.Delete { + return right, nil } - if mergeAction == merkletrie.Delete { - return base, nil + if rightAction == merkletrie.Delete { + return left, nil } - if bytes.Compare(base.To().Hash(), merged.To().Hash()) < 0 { - return base, nil + if bytes.Compare(left.To().Hash(), right.To().Hash()) < 0 { + return left, nil } - return merged, nil + return right, nil } func ForbidResolver(_ IChange, _ IChange) (IChange, error) { @@ -47,58 +47,24 @@ type BaseChange struct { type Conflict struct { Path string - Base BaseChange - Merge BaseChange + Left BaseChange + Right BaseChange } -func OneSideResolver(isBase bool) ConflictResolver { - return func(base IChange, merge IChange) (IChange, error) { - if isBase { - return base, nil +func OneSideResolver(useLeft bool) ConflictResolver { + return func(left IChange, right IChange) (IChange, error) { + if useLeft { + return left, nil } - return merge, nil + return right, nil } } func ResolveFromSelector(resolveMsg map[string]string) ConflictResolver { - return func(base IChange, merge IChange) (IChange, error) { - if resolveMsg[base.Path()] == "base" { - return base, nil + return func(left IChange, right IChange) (IChange, error) { + if resolveMsg[left.Path()] == "left" { + return left, nil } - return merge, nil - } -} - -func ConflictCollector() ConflictResolver { - changes := make([]Conflict, 0) - return func(base IChange, merge IChange) (IChange, error) { - baseAct, err := base.Action() - if err != nil { - return nil, err - } - baseHash := hash.Empty - if baseAct != merkletrie.Delete { - baseHash = base.To().Hash() - } - mergeAct, err := merge.Action() - if err != nil { - return nil, err - } - mergeHash := hash.Empty - if mergeAct != merkletrie.Delete { - mergeHash = merge.To().Hash() - } - changes = append(changes, Conflict{ - Path: base.Path(), - Base: BaseChange{ - Action: baseAct, - Hash: baseHash, - }, - Merge: BaseChange{ - Action: mergeAct, - Hash: mergeHash, - }, - }) - return base, nil + return right, nil } } diff --git a/versionmgr/conflict_test.go b/versionmgr/conflict_test.go new file mode 100644 index 00000000..5f62024f --- /dev/null +++ b/versionmgr/conflict_test.go @@ -0,0 +1,126 @@ +package versionmgr + +import ( + "testing" + + "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" + + "github.com/stretchr/testify/require" +) + +func TestLeastHashResolve(t *testing.T) { + t.Run("select least hash", func(t *testing.T) { + ch1, err := makeMockChange("1|a.txt|h1") + require.NoError(t, err) + ch2, err := makeMockChange("1|a.txt|h2") + require.NoError(t, err) + + chSelect, err := LeastHashResolve(ch1, ch2) + require.NoError(t, err) + require.Equal(t, chSelect.To().Hash(), ch1.To().Hash()) + }) + t.Run("left is delete", func(t *testing.T) { + ch1, err := makeMockChange("2|a.txt|h1") + require.NoError(t, err) + ch2, err := makeMockChange("1|a.txt|h2") + require.NoError(t, err) + + chSelect, err := LeastHashResolve(ch1, ch2) + require.NoError(t, err) + require.Equal(t, chSelect.To().Hash(), ch2.To().Hash()) + }) + t.Run("right is delete", func(t *testing.T) { + ch1, err := makeMockChange("1|a.txt|h1") + require.NoError(t, err) + ch2, err := makeMockChange("2|a.txt|h2") + require.NoError(t, err) + + chSelect, err := LeastHashResolve(ch1, ch2) + require.NoError(t, err) + require.Equal(t, chSelect.To().Hash(), ch1.To().Hash()) + }) +} + +func TestForbidResolver(t *testing.T) { + _, err := ForbidResolver(nil, nil) + require.Error(t, err) +} + +func TestOneSideResolver(t *testing.T) { + t.Run("select left", func(t *testing.T) { + resolver := OneSideResolver(true) + ch1, err := makeMockChange("1|a.txt|h1") + require.NoError(t, err) + ch2, err := makeMockChange("1|a.txt|h2") + require.NoError(t, err) + + chSelect, err := resolver(ch1, ch2) + require.NoError(t, err) + require.Equal(t, chSelect.To().Hash(), ch1.To().Hash()) + }) + t.Run("select right", func(t *testing.T) { + resolver := OneSideResolver(false) + ch1, err := makeMockChange("1|a.txt|h1") + require.NoError(t, err) + ch2, err := makeMockChange("1|a.txt|h2") + require.NoError(t, err) + + chSelect, err := resolver(ch1, ch2) + require.NoError(t, err) + require.Equal(t, chSelect.To().Hash(), ch2.To().Hash()) + }) +} + +func TestResolveFromSelector(t *testing.T) { + resolver := ResolveFromSelector(map[string]string{ + "a.txt": "left", + "b.txt": "right", + "c.txt": "right", + }) + changeData1 := ` +1|a.txt|h1 +3|b.txt|h3 +3|c.txt|h5 +` + changeSet1, err := makeMockChanges(changeData1) + require.NoError(t, err) + changeData2 := ` +1|a.txt|h2 +3|b.txt|h4 +2|c.txt|h1 +` + changeSet2, err := makeMockChanges(changeData2) + require.NoError(t, err) + iter := NewChangesPairIter(changeSet1, changeSet2) + + { + change, err := iter.Next() + require.NoError(t, err) + selectCh, err := resolver(change.Left, change.Right) + require.NoError(t, err) + selectAct, err := selectCh.Action() + require.NoError(t, err) + require.Equal(t, merkletrie.Insert, selectAct) + require.Equal(t, "h1", string(selectCh.To().Hash())) + } + { + change, err := iter.Next() + require.NoError(t, err) + selectCh, err := resolver(change.Left, change.Right) + require.NoError(t, err) + selectAct, err := selectCh.Action() + require.NoError(t, err) + require.Equal(t, merkletrie.Modify, selectAct) + require.Equal(t, "h4", string(selectCh.To().Hash())) + } + { + change, err := iter.Next() + require.NoError(t, err) + selectCh, err := resolver(change.Left, change.Right) + require.NoError(t, err) + selectAct, err := selectCh.Action() + require.NoError(t, err) + require.Equal(t, merkletrie.Delete, selectAct) + } + require.False(t, iter.Has()) +} diff --git a/versionmgr/merge_base_test.go b/versionmgr/merge_base_test.go index 0e94ad4c..a17e546e 100644 --- a/versionmgr/merge_base_test.go +++ b/versionmgr/merge_base_test.go @@ -15,8 +15,8 @@ import ( func TestCommitNodeMergeBase(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() repoID := uuid.New() commitRepo := models.NewCommitRepo(db, repoID) diff --git a/versionmgr/work_repo_diff_test.go b/versionmgr/work_repo_diff_test.go index 48412059..fe5add3e 100644 --- a/versionmgr/work_repo_diff_test.go +++ b/versionmgr/work_repo_diff_test.go @@ -14,8 +14,8 @@ import ( func TestWorkRepositoryDiffCommit(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() repo := models.NewRepo(db) adapter := mem.New(ctx) diff --git a/versionmgr/work_repo_merge_test.go b/versionmgr/work_repo_merge_test.go index a59e54f4..4a04c3a4 100644 --- a/versionmgr/work_repo_merge_test.go +++ b/versionmgr/work_repo_merge_test.go @@ -25,8 +25,8 @@ import ( func TestCommitOpMerge(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() adapter := mem.New(ctx) repo := models.NewRepo(db) @@ -154,8 +154,8 @@ func TestCommitOpMerge(t *testing.T) { // B-------G func TestCrissCrossMerge(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() repo := models.NewRepo(db) adapter := mem.New(ctx) diff --git a/versionmgr/work_repo_test.go b/versionmgr/work_repo_test.go index 0d6fd30b..bd0b86c5 100644 --- a/versionmgr/work_repo_test.go +++ b/versionmgr/work_repo_test.go @@ -27,8 +27,8 @@ import ( func TestTreeWriteBlob(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() adapter := mem.New(ctx) repo := models.NewRepo(db) @@ -68,8 +68,8 @@ func TestNewWorkRepositoryFromConfig(t *testing.T) { tmpDir, err := os.MkdirTemp(os.TempDir(), "*") require.NoError(t, err) - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() repo := models.NewRepo(db) user, err := makeUser(ctx, repo.UserRepo(), "admin") @@ -174,8 +174,8 @@ func TestNewWorkRepositoryFromConfig(t *testing.T) { func TestWorkRepositoryRootTree(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() repo := models.NewRepo(db) user, err := makeUser(ctx, repo.UserRepo(), "admin") @@ -210,8 +210,8 @@ func TestWorkRepositoryRootTree(t *testing.T) { func TestWorkRepositoryRevert(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() repo := models.NewRepo(db) adapter := mem.New(ctx) @@ -355,8 +355,8 @@ func TestWorkRepositoryRevert(t *testing.T) { func TestWorkRepositoryMergeState(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() repo := models.NewRepo(db) user, err := makeUser(ctx, repo.UserRepo(), "admin") diff --git a/versionmgr/worktree_test.go b/versionmgr/worktree_test.go index 1d8a6fe5..6c6b1899 100644 --- a/versionmgr/worktree_test.go +++ b/versionmgr/worktree_test.go @@ -13,10 +13,10 @@ import ( "github.com/stretchr/testify/require" ) -func TestWorkTreeTreeOp(t *testing.T) { +func TestWorkTree(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() repoID := uuid.New() objRepo := models.NewFileTree(db, repoID) @@ -101,8 +101,8 @@ func TestWorkTreeTreeOp(t *testing.T) { func TestRemoveEntry(t *testing.T) { ctx := context.Background() - postgres, _, db := testhelper.SetupDatabase(ctx, t) - defer postgres.Stop() //nolint + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() repoID := uuid.New() objRepo := models.NewFileTree(db, repoID) From d85d09b0403356530f8ad24dffe52f5bd305157e Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Wed, 10 Jan 2024 15:17:03 +0800 Subject: [PATCH 141/210] feat: change time to timestamp --- api/jiaozifs.gen.go | 229 +++++++++++++------------- api/swagger.yml | 119 +++++++------ controller/branch_ctl.go | 12 +- controller/commit_ctl.go | 38 ++++- controller/merge_request_ctl.go | 18 +- controller/repository_ctl.go | 79 +++------ controller/user_ctl.go | 10 +- controller/wip_ctl.go | 26 ++- integrationtest/merge_request_test.go | 7 +- integrationtest/repo_test.go | 16 +- models/branch.go | 4 +- models/commit.go | 4 +- models/merge_request.go | 4 +- models/repository.go | 4 +- models/tag.go | 4 +- models/tree.go | 8 +- models/user.go | 8 +- models/wip.go | 4 +- utils/pagination.go | 5 +- 19 files changed, 299 insertions(+), 300 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 5b1ec984..f0684a4b 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -15,7 +15,6 @@ import ( "net/url" "path" "strings" - "time" "github.com/getkin/kin-openapi/openapi3" "github.com/go-chi/chi/v5" @@ -68,13 +67,13 @@ type AuthenticationToken struct { // Branch defines model for Branch. type Branch struct { CommitHash string `json:"commit_hash"` - CreatedAt time.Time `json:"created_at"` + CreatedAt int64 `json:"created_at"` CreatorId openapi_types.UUID `json:"creator_id"` Description *string `json:"description,omitempty"` Id openapi_types.UUID `json:"id"` Name string `json:"name"` RepositoryId openapi_types.UUID `json:"repository_id"` - UpdatedAt time.Time `json:"updated_at"` + UpdatedAt int64 `json:"updated_at"` } // BranchCreation defines model for BranchCreation. @@ -112,14 +111,14 @@ type ChangePair struct { type Commit struct { Author Signature `json:"author"` Committer Signature `json:"committer"` - CreatedAt time.Time `json:"created_at"` + CreatedAt int64 `json:"created_at"` Hash string `json:"hash"` MergeTag string `json:"merge_tag"` Message string `json:"message"` ParentHashes []string `json:"parent_hashes"` RepositoryId openapi_types.UUID `json:"repository_id"` TreeHash string `json:"tree_hash"` - UpdatedAt time.Time `json:"updated_at"` + UpdatedAt int64 `json:"updated_at"` } // CreateMergeRequest defines model for CreateMergeRequest. @@ -140,12 +139,12 @@ type CreateRepository struct { // FullTreeEntry defines model for FullTreeEntry. type FullTreeEntry struct { - CreatedAt time.Time `json:"created_at"` - Hash string `json:"hash"` - IsDir bool `json:"is_dir"` - Name string `json:"name"` - Size int64 `json:"size"` - UpdatedAt time.Time `json:"updated_at"` + CreatedAt int64 `json:"created_at"` + Hash string `json:"hash"` + IsDir bool `json:"is_dir"` + Name string `json:"name"` + Size int64 `json:"size"` + UpdatedAt int64 `json:"updated_at"` } // LoginConfig defines model for LoginConfig. @@ -188,7 +187,7 @@ type MergeMergeRequest struct { // MergeRequest defines model for MergeRequest. type MergeRequest struct { AuthorId openapi_types.UUID `json:"author_id"` - CreatedAt time.Time `json:"created_at"` + CreatedAt int64 `json:"created_at"` Description *string `json:"description,omitempty"` Id openapi_types.UUID `json:"id"` MergeStatus int `json:"merge_status"` @@ -198,14 +197,14 @@ type MergeRequest struct { TargetBranch openapi_types.UUID `json:"target_branch"` TargetRepoId openapi_types.UUID `json:"target_repo_id"` Title string `json:"title"` - UpdatedAt time.Time `json:"updated_at"` + UpdatedAt int64 `json:"updated_at"` } // MergeRequestFullState defines model for MergeRequestFullState. type MergeRequestFullState struct { AuthorId openapi_types.UUID `json:"author_id"` Changes []ChangePair `json:"changes"` - CreatedAt time.Time `json:"created_at"` + CreatedAt int64 `json:"created_at"` Description *string `json:"description,omitempty"` Id openapi_types.UUID `json:"id"` MergeStatus int `json:"merge_status"` @@ -215,7 +214,7 @@ type MergeRequestFullState struct { TargetBranch openapi_types.UUID `json:"target_branch"` TargetRepoId openapi_types.UUID `json:"target_repo_id"` Title string `json:"title"` - UpdatedAt time.Time `json:"updated_at"` + UpdatedAt int64 `json:"updated_at"` } // MergeRequestList defines model for MergeRequestList. @@ -262,7 +261,7 @@ type RefType string // Repository defines model for Repository. type Repository struct { - CreatedAt time.Time `json:"created_at"` + CreatedAt int64 `json:"created_at"` CreatorId openapi_types.UUID `json:"creator_id"` Description *string `json:"description,omitempty"` Head string `json:"head"` @@ -271,7 +270,7 @@ type Repository struct { OwnerId openapi_types.UUID `json:"owner_id"` StorageAdapterParams *string `json:"storage_adapter_params,omitempty"` StorageNamespace *string `json:"storage_namespace,omitempty"` - UpdatedAt time.Time `json:"updated_at"` + UpdatedAt int64 `json:"updated_at"` UsePublicStorage bool `json:"use_public_storage"` } @@ -296,7 +295,7 @@ type SetupStateState string type Signature struct { Email openapi_types.Email `json:"email"` Name string `json:"name"` - When time.Time `json:"when"` + When int64 `json:"when"` } // UpdateMergeRequest defines model for UpdateMergeRequest. @@ -320,15 +319,15 @@ type UpdateWip struct { // UserInfo defines model for UserInfo. type UserInfo struct { - CreatedAt time.Time `json:"created_at"` - CurrentSignInAt *time.Time `json:"current_sign_in_at,omitempty"` + CreatedAt int64 `json:"created_at"` + CurrentSignInAt *int64 `json:"current_sign_in_at,omitempty"` CurrentSignInIp *string `json:"current_sign_in_ip,omitempty"` Email openapi_types.Email `json:"email"` Id openapi_types.UUID `json:"id"` - LastSignInAt *time.Time `json:"last_sign_in_at,omitempty"` + LastSignInAt *int64 `json:"last_sign_in_at,omitempty"` LastSignInIp *string `json:"last_sign_in_ip,omitempty"` Name string `json:"name"` - UpdatedAt time.Time `json:"updated_at"` + UpdatedAt int64 `json:"updated_at"` } // UserRegisterInfo defines model for UserRegisterInfo. @@ -351,30 +350,27 @@ type VersionResult struct { // Wip defines model for Wip. type Wip struct { BaseCommit string `json:"base_commit"` - CreatedAt time.Time `json:"created_at"` + CreatedAt int64 `json:"created_at"` CreatorId openapi_types.UUID `json:"creator_id"` CurrentTree string `json:"current_tree"` Id openapi_types.UUID `json:"id"` RefId openapi_types.UUID `json:"ref_id"` RepositoryId openapi_types.UUID `json:"repository_id"` State int `json:"state"` - UpdatedAt time.Time `json:"updated_at"` + UpdatedAt int64 `json:"updated_at"` } // PaginationAmount defines model for PaginationAmount. type PaginationAmount = int -// PaginationBranchAfter defines model for PaginationBranchAfter. -type PaginationBranchAfter = string - -// PaginationCommitAfter defines model for PaginationCommitAfter. -type PaginationCommitAfter = string +// PaginationInt64After defines model for PaginationInt64After. +type PaginationInt64After = int64 // PaginationPrefix defines model for PaginationPrefix. type PaginationPrefix = string -// PaginationRepoAfter defines model for PaginationRepoAfter. -type PaginationRepoAfter = time.Time +// PaginationStringAfter defines model for PaginationStringAfter. +type PaginationStringAfter = string // LoginJSONBody defines parameters for Login. type LoginJSONBody struct { @@ -385,7 +381,7 @@ type LoginJSONBody struct { // ListMergeRequestsParams defines parameters for ListMergeRequests. type ListMergeRequestsParams struct { // After return items after this value - After *PaginationRepoAfter `form:"after,omitempty" json:"after,omitempty"` + After *PaginationInt64After `form:"after,omitempty" json:"after,omitempty"` // Amount how many items to return Amount *PaginationAmount `form:"amount,omitempty" json:"amount,omitempty"` @@ -462,7 +458,7 @@ type ListBranchesParams struct { Prefix *PaginationPrefix `form:"prefix,omitempty" json:"prefix,omitempty"` // After return items after this value - After *PaginationBranchAfter `form:"after,omitempty" json:"after,omitempty"` + After *PaginationStringAfter `form:"after,omitempty" json:"after,omitempty"` // Amount how many items to return Amount *PaginationAmount `form:"amount,omitempty" json:"amount,omitempty"` @@ -477,7 +473,7 @@ type GetCommitChangesParams struct { // GetCommitsInRefParams defines parameters for GetCommitsInRef. type GetCommitsInRefParams struct { // After return items after this value - After *PaginationCommitAfter `form:"after,omitempty" json:"after,omitempty"` + After *PaginationInt64After `form:"after,omitempty" json:"after,omitempty"` // Amount how many items to return Amount *PaginationAmount `form:"amount,omitempty" json:"amount,omitempty"` @@ -510,7 +506,7 @@ type ListRepositoryOfAuthenticatedUserParams struct { Prefix *PaginationPrefix `form:"prefix,omitempty" json:"prefix,omitempty"` // After return items after this value - After *PaginationRepoAfter `form:"after,omitempty" json:"after,omitempty"` + After *PaginationInt64After `form:"after,omitempty" json:"after,omitempty"` // Amount how many items to return Amount *PaginationAmount `form:"amount,omitempty" json:"amount,omitempty"` @@ -522,7 +518,7 @@ type ListRepositoryParams struct { Prefix *PaginationPrefix `form:"prefix,omitempty" json:"prefix,omitempty"` // After return items after this value - After *PaginationRepoAfter `form:"after,omitempty" json:"after,omitempty"` + After *PaginationInt64After `form:"after,omitempty" json:"after,omitempty"` // Amount how many items to return Amount *PaginationAmount `form:"amount,omitempty" json:"amount,omitempty"` @@ -8305,87 +8301,86 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "7v8DAAD//9cankg2hwAA", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index bcc3eada..6fd5f00d 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -26,28 +26,21 @@ components: schema: type: string - PaginationBranchAfter: + PaginationStringAfter: in: query name: after description: return items after this value schema: type: string - PaginationRepoAfter: + PaginationInt64After: in: query name: after description: return items after this value schema: - type: string - format: date-time - - PaginationCommitAfter: - in: query - name: after - description: return items after this value - schema: - type: string - format: date-time-ns + type: integer + format: int64 + PaginationAmount: in: query @@ -224,11 +217,11 @@ components: type: string format: uuid created_at: - type: string - format: date-time + type: integer + format: int64 updated_at: - type: string - format: date-time + type: integer + format: int64 MergeRequestFullState: type: object required: @@ -278,11 +271,11 @@ components: items: $ref: "#/components/schemas/ChangePair" created_at: - type: string - format: date-time + type: integer + format: int64 updated_at: - type: string - format: date-time + type: integer + format: int64 MergeRequestList: type: object required: @@ -322,11 +315,11 @@ components: type: string format: uuid created_at: - type: string - format: date-time + type: integer + format: int64 updated_at: - type: string - format: date-time + type: integer + format: int64 BranchList: type: object required: @@ -405,11 +398,11 @@ components: type: string format: uuid created_at: - type: string - format: date-time + type: integer + format: int64 updated_at: - type: string - format: date-time + type: integer + format: int64 Blob: type: object required: @@ -440,11 +433,11 @@ components: additionalProperties: type: string created_at: - type: string - format: date-time + type: integer + format: int64 updated_at: - type: string - format: date-time + type: integer + format: int64 Signature: type: object required: @@ -458,8 +451,8 @@ components: type: string format: email when: - type: string - format: date-time + type: integer + format: int64 Commit: type: object required: @@ -494,11 +487,11 @@ components: items: type: string created_at: - type: string - format: date-time + type: integer + format: int64 updated_at: - type: string - format: date-time + type: integer + format: int64 TreeEntry: type: object required: @@ -532,11 +525,11 @@ components: type: integer format: int64 created_at: - type: string - format: date-time + type: integer + format: int64 updated_at: - type: string - format: date-time + type: integer + format: int64 TreeNode: type: object required: @@ -565,11 +558,11 @@ components: items: $ref: "#/components/schemas/TreeEntry" created_at: - type: string - format: date-time + type: integer + format: int64 updated_at: - type: string - format: date-time + type: integer + format: int64 Wip: type: object required: @@ -603,11 +596,11 @@ components: type: string format: uuid created_at: - type: string - format: date-time + type: integer + format: int64 updated_at: - type: string - format: date-time + type: integer + format: int64 UpdateWip: type: object properties: @@ -676,11 +669,11 @@ components: type: string format: email current_sign_in_at: - type: string - format: date-time + type: integer + format: int64 last_sign_in_at: - type: string - format: date-time + type: integer + format: int64 current_sign_in_ip: type: string format: ipv4 @@ -688,11 +681,11 @@ components: type: string format: ipv4 created_at: - type: string - format: date-time + type: integer + format: int64 updated_at: - type: string - format: date-time + type: integer + format: int64 UserRegisterInfo: type: object required: @@ -1560,7 +1553,7 @@ paths: operationId: getCommitsInRef summary: get commits in ref parameters: - - $ref: "#/components/parameters/PaginationCommitAfter" + - $ref: "#/components/parameters/PaginationInt64After" - $ref: "#/components/parameters/PaginationAmount" - in: query name: refName @@ -1662,7 +1655,7 @@ paths: operationId: listMergeRequests summary: get list of merge request in repository parameters: - - $ref: "#/components/parameters/PaginationRepoAfter" + - $ref: "#/components/parameters/PaginationInt64After" - $ref: "#/components/parameters/PaginationAmount" - in: query name: state @@ -1833,7 +1826,7 @@ paths: summary: list repository in specific owner parameters: - $ref: "#/components/parameters/PaginationPrefix" - - $ref: "#/components/parameters/PaginationRepoAfter" + - $ref: "#/components/parameters/PaginationInt64After" - $ref: "#/components/parameters/PaginationAmount" responses: 200: @@ -1857,7 +1850,7 @@ paths: summary: list repository parameters: - $ref: "#/components/parameters/PaginationPrefix" - - $ref: "#/components/parameters/PaginationRepoAfter" + - $ref: "#/components/parameters/PaginationInt64After" - $ref: "#/components/parameters/PaginationAmount" responses: 200: @@ -1918,7 +1911,7 @@ paths: summary: list branches parameters: - $ref: "#/components/parameters/PaginationPrefix" - - $ref: "#/components/parameters/PaginationBranchAfter" + - $ref: "#/components/parameters/PaginationStringAfter" - $ref: "#/components/parameters/PaginationAmount" responses: 200: diff --git a/controller/branch_ctl.go b/controller/branch_ctl.go index 8cbcf177..f21677c5 100644 --- a/controller/branch_ctl.go +++ b/controller/branch_ctl.go @@ -103,13 +103,13 @@ func (bct BranchController) ListBranches(ctx context.Context, w *api.JiaozifsRes for _, branch := range branches { r := api.Branch{ CommitHash: branch.CommitHash.Hex(), - CreatedAt: branch.CreatedAt, + CreatedAt: branch.CreatedAt.UnixMilli(), CreatorId: branch.CreatorID, Description: branch.Description, Id: branch.ID, Name: branch.Name, RepositoryId: branch.RepositoryID, - UpdatedAt: branch.UpdatedAt, + UpdatedAt: branch.UpdatedAt.UnixMilli(), } results = append(results, r) } @@ -183,13 +183,13 @@ func (bct BranchController) CreateBranch(ctx context.Context, w *api.JiaozifsRes w.JSON(api.Branch{ CommitHash: newBranch.CommitHash.Hex(), - CreatedAt: newBranch.CreatedAt, + CreatedAt: newBranch.CreatedAt.UnixMilli(), CreatorId: newBranch.CreatorID, Description: newBranch.Description, Id: newBranch.ID, Name: newBranch.Name, RepositoryId: newBranch.RepositoryID, - UpdatedAt: newBranch.UpdatedAt, + UpdatedAt: newBranch.UpdatedAt.UnixMilli(), }, http.StatusCreated) } @@ -271,12 +271,12 @@ func (bct BranchController) GetBranch(ctx context.Context, w *api.JiaozifsRespon } w.JSON(api.Branch{ CommitHash: ref.CommitHash.Hex(), - CreatedAt: ref.CreatedAt, + CreatedAt: ref.CreatedAt.UnixMilli(), CreatorId: ref.CreatorID, Description: ref.Description, Id: ref.ID, Name: ref.Name, RepositoryId: ref.RepositoryID, - UpdatedAt: ref.UpdatedAt, + UpdatedAt: ref.UpdatedAt.UnixMilli(), }) } diff --git a/controller/commit_ctl.go b/controller/commit_ctl.go index 42b71bfb..54dab564 100644 --- a/controller/commit_ctl.go +++ b/controller/commit_ctl.go @@ -7,6 +7,8 @@ import ( "net/http" "strings" + openapi_types "github.com/oapi-codegen/runtime/types" + "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/auth" "github.com/jiaozifs/jiaozifs/block/params" @@ -123,7 +125,18 @@ func (commitCtl CommitController) GetEntriesInRef(ctx context.Context, w *api.Ji w.Error(err) return } - w.JSON(treeEntry) + apiTreeEntries := make([]api.FullTreeEntry, len(treeEntry)) + for index, entry := range treeEntry { + apiTreeEntries[index] = api.FullTreeEntry{ + CreatedAt: entry.CreatedAt.UnixMilli(), + Hash: entry.Hash.Hex(), + IsDir: entry.IsDir, + Name: entry.Name, + Size: entry.Size, + UpdatedAt: entry.UpdatedAt.UnixMilli(), + } + } + w.JSON(apiTreeEntries) } func (commitCtl CommitController) CompareCommit(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, basehead string, params api.CompareCommitParams) { @@ -237,3 +250,26 @@ func (commitCtl CommitController) GetCommitChanges(ctx context.Context, w *api.J } w.JSON(changesResp) } + +func commitToDto(commit *models.Commit) *api.Commit { + return &api.Commit{ + Author: api.Signature{ + Email: openapi_types.Email(commit.Author.Email), + Name: commit.Author.Name, + When: commit.Author.When.UnixMilli(), + }, + Committer: api.Signature{ + Email: openapi_types.Email(commit.Committer.Email), + Name: commit.Committer.Name, + When: commit.Committer.When.UnixMilli(), + }, + CreatedAt: commit.CreatedAt.UnixMilli(), + Hash: commit.Hash.Hex(), + MergeTag: commit.MergeTag, + Message: commit.Message, + ParentHashes: hash.HexArrayOfHashes(commit.ParentHashes...), + RepositoryId: commit.RepositoryID, + TreeHash: commit.TreeHash.Hex(), + UpdatedAt: commit.UpdatedAt.UnixMilli(), + } +} diff --git a/controller/merge_request_ctl.go b/controller/merge_request_ctl.go index 565d3c1f..736f0111 100644 --- a/controller/merge_request_ctl.go +++ b/controller/merge_request_ctl.go @@ -57,7 +57,7 @@ func (mrCtl MergeRequestController) ListMergeRequests(ctx context.Context, w *ap } if params.After != nil { - listParams.SetAfter(*params.After) + listParams.SetAfter(time.UnixMilli(*params.After)) } pageAmount := utils.IntValue(params.Amount) if pageAmount > utils.DefaultMaxPerPage || pageAmount <= 0 { @@ -83,8 +83,8 @@ func (mrCtl MergeRequestController) ListMergeRequests(ctx context.Context, w *ap SourceRepoId: mr.SourceRepoID, TargetBranch: mr.TargetBranchID, TargetRepoId: mr.TargetRepoID, - CreatedAt: mr.CreatedAt, - UpdatedAt: mr.UpdatedAt, + CreatedAt: mr.CreatedAt.UnixMilli(), + UpdatedAt: mr.UpdatedAt.UnixMilli(), } } pagMag := utils.PaginationFor(hasMore, results, "UpdatedAt") @@ -202,8 +202,8 @@ func (mrCtl MergeRequestController) CreateMergeRequest(ctx context.Context, w *a SourceRepoId: mrModel.SourceRepoID, TargetBranch: mrModel.TargetBranchID, TargetRepoId: mrModel.TargetRepoID, - CreatedAt: mrModel.CreatedAt, - UpdatedAt: mrModel.UpdatedAt, + CreatedAt: mrModel.CreatedAt.UnixMilli(), + UpdatedAt: mrModel.UpdatedAt.UnixMilli(), } resp.Changes, err = changePairToDTO(changePairs) @@ -212,7 +212,7 @@ func (mrCtl MergeRequestController) CreateMergeRequest(ctx context.Context, w *a return } //get merge state - w.JSON(mrModel, http.StatusCreated) + w.JSON(resp, http.StatusCreated) } func (mrCtl MergeRequestController) GetMergeRequest(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, mrSeq uint64) { operator, err := auth.GetOperator(ctx) @@ -285,8 +285,8 @@ func (mrCtl MergeRequestController) GetMergeRequest(ctx context.Context, w *api. SourceRepoId: mergeRequest.SourceRepoID, TargetBranch: mergeRequest.TargetBranchID, TargetRepoId: mergeRequest.TargetRepoID, - CreatedAt: mergeRequest.CreatedAt, - UpdatedAt: mergeRequest.UpdatedAt, + CreatedAt: mergeRequest.CreatedAt.UnixMilli(), + UpdatedAt: mergeRequest.UpdatedAt.UnixMilli(), } resp.Changes, err = changePairToDTO(changePairs) if err != nil { @@ -403,7 +403,7 @@ func (mrCtl MergeRequestController) Merge(ctx context.Context, w *api.JiaozifsRe return } - w.JSON(commit) + w.JSON(commitToDto(commit)) } func changePairToDTO(pairs []*versionmgr.ChangePair) ([]api.ChangePair, error) { diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index 90d387ac..ec31a598 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -20,7 +20,6 @@ import ( "github.com/jiaozifs/jiaozifs/utils" "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/jiaozifs/jiaozifs/versionmgr" - openapi_types "github.com/oapi-codegen/runtime/types" "go.uber.org/fx" ) @@ -68,7 +67,7 @@ func (repositoryCtl RepositoryController) ListRepositoryOfAuthenticatedUser(ctx listRepoParams.SetName(*params.Prefix, models.PrefixMatch) } if params.After != nil { - listRepoParams.SetAfter(*params.After) + listRepoParams.SetAfter(time.UnixMilli(utils.Int64Value(params.After))) } pageAmount := utils.IntValue(params.Amount) if pageAmount > utils.DefaultMaxPerPage || pageAmount <= 0 { @@ -85,16 +84,7 @@ func (repositoryCtl RepositoryController) ListRepositoryOfAuthenticatedUser(ctx } results := make([]api.Repository, 0, len(repositories)) for _, repo := range repositories { - r := api.Repository{ - CreatedAt: repo.CreatedAt, - CreatorId: repo.CreatorID, - Description: repo.Description, - Head: repo.HEAD, - Id: repo.ID, - Name: repo.Name, - UpdatedAt: repo.UpdatedAt, - } - results = append(results, r) + results = append(results, *repositoryToDto(repo)) } pagMag := utils.PaginationFor(hasMore, results, "UpdatedAt") pagination := api.Pagination{ @@ -131,7 +121,7 @@ func (repositoryCtl RepositoryController) ListRepository(ctx context.Context, w listRepoParams.SetName(*params.Prefix, models.PrefixMatch) } if params.After != nil { - listRepoParams.SetAfter(*params.After) + listRepoParams.SetAfter(time.UnixMilli(*params.After)) } pageAmount := utils.IntValue(params.Amount) if pageAmount > utils.DefaultMaxPerPage || pageAmount <= 0 { @@ -147,16 +137,7 @@ func (repositoryCtl RepositoryController) ListRepository(ctx context.Context, w } results := make([]api.Repository, 0, len(repositories)) for _, repo := range repositories { - r := api.Repository{ - CreatedAt: repo.CreatedAt, - CreatorId: repo.CreatorID, - Description: repo.Description, - Head: repo.HEAD, - Id: repo.ID, - Name: repo.Name, - UpdatedAt: repo.UpdatedAt, - } - results = append(results, r) + results = append(results, *repositoryToDto(repo)) } pagMag := utils.PaginationFor(hasMore, results, "UpdatedAt") pagination := api.Pagination{ @@ -244,15 +225,7 @@ func (repositoryCtl RepositoryController) CreateRepository(ctx context.Context, return } - w.JSON(api.Repository{ - CreatedAt: createdRepo.CreatedAt, - CreatorId: createdRepo.CreatorID, - Description: createdRepo.Description, - Head: createdRepo.HEAD, - Id: createdRepo.ID, - Name: createdRepo.Name, - UpdatedAt: createdRepo.UpdatedAt, - }) + w.JSON(repositoryToDto(createdRepo)) } func (repositoryCtl RepositoryController) DeleteRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string) { @@ -349,7 +322,7 @@ func (repositoryCtl RepositoryController) GetRepository(ctx context.Context, w * return } - w.JSON(repo) + w.JSON(repositoryToDto(repo)) } func (repositoryCtl RepositoryController) UpdateRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.UpdateRepositoryJSONRequestBody, ownerName string, repositoryName string) { @@ -451,11 +424,7 @@ func (repositoryCtl RepositoryController) GetCommitsInRef(ctx context.Context, w commit, err := iter.Next() if err == nil { if params.After != nil { - parseTime, err := time.Parse(time.RFC3339Nano, *params.After) - if err != nil { - w.Error(err) - return - } + parseTime := time.UnixMilli(*params.After) if commit.Commit().Committer.When.Add(time.Nanosecond).After(parseTime) { continue } @@ -464,27 +433,7 @@ func (repositoryCtl RepositoryController) GetCommitsInRef(ctx context.Context, w break } modelCommit := commit.Commit() - commits = append(commits, api.Commit{ - RepositoryId: modelCommit.RepositoryID, - Author: api.Signature{ - Email: openapi_types.Email(modelCommit.Author.Email), - Name: modelCommit.Author.Name, - When: modelCommit.Author.When, - }, - - Committer: api.Signature{ - Email: openapi_types.Email(modelCommit.Committer.Email), - Name: modelCommit.Committer.Name, - When: modelCommit.Committer.When, - }, - CreatedAt: modelCommit.CreatedAt, - Hash: modelCommit.Hash.Hex(), - MergeTag: modelCommit.MergeTag, - Message: modelCommit.Message, - ParentHashes: hash.HexArrayOfHashes(modelCommit.ParentHashes...), - TreeHash: modelCommit.TreeHash.Hex(), - UpdatedAt: modelCommit.UpdatedAt, - }) + commits = append(commits, *commitToDto(modelCommit)) continue } if err == io.EOF { @@ -495,3 +444,15 @@ func (repositoryCtl RepositoryController) GetCommitsInRef(ctx context.Context, w } w.JSON(commits) } + +func repositoryToDto(repository *models.Repository) *api.Repository { + return &api.Repository{ + CreatedAt: repository.CreatedAt.UnixMilli(), + CreatorId: repository.CreatorID, + Description: repository.Description, + Head: repository.HEAD, + Id: repository.ID, + Name: repository.Name, + UpdatedAt: repository.UpdatedAt.UnixMilli(), + } +} diff --git a/controller/user_ctl.go b/controller/user_ctl.go index a4489d9b..1a4f842c 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -6,6 +6,8 @@ import ( "net/http" "time" + "github.com/jiaozifs/jiaozifs/utils" + "github.com/go-openapi/swag" "github.com/gorilla/sessions" logging "github.com/ipfs/go-log/v2" @@ -105,12 +107,12 @@ func (userCtl UserController) GetUserInfo(ctx context.Context, w *api.JiaozifsRe userInfo := api.UserInfo{ Name: user.Name, Email: openapitypes.Email(user.Email), - CurrentSignInAt: &user.CurrentSignInAt, + CurrentSignInAt: utils.Int64(user.CurrentSignInAt.UnixMilli()), CurrentSignInIp: &user.CurrentSignInIP, - LastSignInAt: &user.LastSignInAt, + LastSignInAt: utils.Int64(user.LastSignInAt.UnixMilli()), LastSignInIp: &user.LastSignInIP, - UpdatedAt: user.UpdatedAt, - CreatedAt: user.CreatedAt, + UpdatedAt: user.UpdatedAt.UnixMilli(), + CreatedAt: user.CreatedAt.UnixMilli(), } w.JSON(userInfo) } diff --git a/controller/wip_ctl.go b/controller/wip_ctl.go index 1bb5a4e2..0995d253 100644 --- a/controller/wip_ctl.go +++ b/controller/wip_ctl.go @@ -66,9 +66,9 @@ func (wipCtl WipController) GetWip(ctx context.Context, w *api.JiaozifsResponse, return } if isNew { - w.JSON(wip, http.StatusCreated) + w.JSON(wipToDto(wip), http.StatusCreated) } - w.JSON(wip) + w.JSON(wipToDto(wip)) } // ListWip return wips of branches, operator only see himself wips in specific repository @@ -102,7 +102,11 @@ func (wipCtl WipController) ListWip(ctx context.Context, w *api.JiaozifsResponse return } - w.JSON(wips) + apiWips := make([]*api.Wip, len(wips)) + for index, wip := range wips { + apiWips[index] = wipToDto(wip) + } + w.JSON(apiWips) } // CommitWip commit wip to branch, operator only could operator himself wip @@ -148,7 +152,7 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon return } - w.JSON(workRepo.CurWip(), http.StatusCreated) + w.JSON(wipToDto(workRepo.CurWip()), http.StatusCreated) } func (wipCtl WipController) UpdateWip(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.UpdateWipJSONRequestBody, ownerName string, repositoryName string, params api.UpdateWipParams) { @@ -391,3 +395,17 @@ func (wipCtl WipController) RevertWipChanges(ctx context.Context, w *api.Jiaozif w.OK() } + +func wipToDto(wip *models.WorkingInProcess) *api.Wip { + return &api.Wip{ + BaseCommit: wip.BaseCommit.Hex(), + CreatedAt: wip.CreatedAt.UnixMilli(), + CreatorId: wip.CreatorID, + CurrentTree: wip.CurrentTree.Hex(), + Id: wip.ID, + RefId: wip.RefID, + RepositoryId: wip.RepositoryID, + State: int(wip.State), + UpdatedAt: wip.UpdatedAt.UnixMilli(), + } +} diff --git a/integrationtest/merge_request_test.go b/integrationtest/merge_request_test.go index 04554373..1946fe44 100644 --- a/integrationtest/merge_request_test.go +++ b/integrationtest/merge_request_test.go @@ -325,7 +325,7 @@ func MergeRequestSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("success to list page merge quest", func() { - var after *time.Time + var after *int64 for i := 0; i < 6; i++ { resp, err := client.ListMergeRequests(ctx, userName, repoName, &api.ListMergeRequestsParams{ After: after, @@ -341,9 +341,10 @@ func MergeRequestSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.ShouldBeFalse((*result.JSON200).Pagination.HasMore) } else { convey.ShouldBeTrue((*result.JSON200).Pagination.HasMore) - next, err := time.Parse(`2006-01-02 15:04:05.999999999 -0700 MST`, (*result.JSON200).Pagination.NextOffset) + val, err := strconv.ParseInt((*result.JSON200).Pagination.NextOffset, 10, 64) convey.So(err, convey.ShouldBeNil) - after = &next + next := time.UnixMilli(val) + after = utils.Int64(next.UnixMilli()) } } }) diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index 9698c9e0..dfcecfa4 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "net/http" - "time" "github.com/jiaozifs/jiaozifs/api" apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" @@ -147,7 +146,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(len(listRepos.JSON200.Results), convey.ShouldEqual, 2) newResp, err := client.ListRepositoryOfAuthenticatedUser(ctx, &api.ListRepositoryOfAuthenticatedUserParams{ - After: utils.Time(listRepos.JSON200.Results[0].UpdatedAt), + After: utils.Int64(listRepos.JSON200.Results[0].UpdatedAt), Amount: utils.Int(1), }) convey.So(err, convey.ShouldBeNil) @@ -205,7 +204,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(len(listRepos.JSON200.Results), convey.ShouldEqual, 2) newResp, err := client.ListRepository(ctx, userName, &api.ListRepositoryParams{ - After: utils.Time(listRepos.JSON200.Results[0].UpdatedAt), + After: utils.Int64(listRepos.JSON200.Results[0].UpdatedAt), Amount: utils.Int(1), }) convey.So(err, convey.ShouldBeNil) @@ -430,7 +429,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So((*result.JSON200)[0].Message, convey.ShouldEqual, "third commit") newResp, err := client.GetCommitsInRef(ctx, userName, repoName, &api.GetCommitsInRefParams{ - After: utils.String((*result.JSON200)[0].Committer.When.Format(time.RFC3339Nano)), + After: utils.Int64((*result.JSON200)[0].Committer.When), Amount: utils.Int(1), RefName: utils.String(controller.DefaultBranchName), }) @@ -442,15 +441,6 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(*newResult.JSON200, convey.ShouldHaveLength, 1) convey.So((*newResult.JSON200)[0].Message, convey.ShouldEqual, "second commit") }) - - c.Convey("failed get commits by wrong params", func() { - resp, err := client.GetCommitsInRef(ctx, userName, repoName, &api.GetCommitsInRefParams{ - After: utils.String("123"), - RefName: utils.String(controller.DefaultBranchName), - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusInternalServerError) - }) }) c.Convey("delete repository", func(c convey.C) { diff --git a/models/branch.go b/models/branch.go index 3032037a..bb4580e7 100644 --- a/models/branch.go +++ b/models/branch.go @@ -22,8 +22,8 @@ type Branch struct { // CreatorID who create this branch CreatorID uuid.UUID `bun:"creator_id,type:uuid,notnull" json:"creator_id"` - CreatedAt time.Time `bun:"created_at,notnull" json:"created_at"` - UpdatedAt time.Time `bun:"updated_at,notnull" json:"updated_at"` + CreatedAt time.Time `bun:"created_at,type:timestamp,notnull" json:"created_at"` + UpdatedAt time.Time `bun:"updated_at,type:timestamp,notnull" json:"updated_at"` } type GetBranchParams struct { diff --git a/models/commit.go b/models/commit.go index 43cdf796..8f1c6b08 100644 --- a/models/commit.go +++ b/models/commit.go @@ -39,8 +39,8 @@ type Commit struct { // ParentHashes are the hashes of the parent commits of the commit. ParentHashes []hash.Hash `bun:"parent_hashes,type:bytea[]" json:"parent_hashes"` - CreatedAt time.Time `bun:"created_at,notnull" json:"created_at"` - UpdatedAt time.Time `bun:"updated_at,notnull" json:"updated_at"` + CreatedAt time.Time `bun:"created_at,type:timestamp,notnull" json:"created_at"` + UpdatedAt time.Time `bun:"updated_at,type:timestamp,notnull" json:"updated_at"` } func (commit *Commit) GetHash() (hash.Hash, error) { diff --git a/models/merge_request.go b/models/merge_request.go index 6052fac0..7e38f3a7 100644 --- a/models/merge_request.go +++ b/models/merge_request.go @@ -31,8 +31,8 @@ type MergeRequest struct { AuthorID uuid.UUID `bun:"author_id,type:bytea,notnull" json:"author_id"` - CreatedAt time.Time `bun:"created_at,notnull" json:"created_at"` - UpdatedAt time.Time `bun:"updated_at,notnull" json:"updated_at"` + CreatedAt time.Time `bun:"created_at,type:timestamp,notnull" json:"created_at"` + UpdatedAt time.Time `bun:"updated_at,type:timestamp,notnull" json:"updated_at"` } type GetMergeRequestParams struct { diff --git a/models/repository.go b/models/repository.go index 80859c83..542bc551 100644 --- a/models/repository.go +++ b/models/repository.go @@ -23,8 +23,8 @@ type Repository struct { Description *string `bun:"description" json:"description,omitempty"` CreatorID uuid.UUID `bun:"creator_id,type:uuid,notnull" json:"creator_id"` - CreatedAt time.Time `bun:"created_at,notnull" json:"created_at"` - UpdatedAt time.Time `bun:"updated_at,notnull" json:"updated_at"` + CreatedAt time.Time `bun:"created_at,type:timestamp,notnull" json:"created_at"` + UpdatedAt time.Time `bun:"updated_at,type:timestamp,notnull" json:"updated_at"` } type GetRepoParams struct { diff --git a/models/tag.go b/models/tag.go index 97a4a743..f48454f5 100644 --- a/models/tag.go +++ b/models/tag.go @@ -26,8 +26,8 @@ type Tag struct { // Message is the tag message, contains arbitrary text. Message string `bun:"message" json:"message"` - CreatedAt time.Time `bun:"created_at" json:"created_at"` - UpdatedAt time.Time `bun:"updated_at" json:"updated_at"` + CreatedAt time.Time `bun:"created_at,type:timestamp,notnull" json:"created_at"` + UpdatedAt time.Time `bun:"updated_at,type:timestamp,notnull" json:"updated_at"` } type ITagRepo interface { diff --git a/models/tree.go b/models/tree.go index 51f1f97d..f0caf382 100644 --- a/models/tree.go +++ b/models/tree.go @@ -83,8 +83,8 @@ type Blob struct { Size int64 `bun:"size"` Properties Property `bun:"properties,type:jsonb,notnull"` - CreatedAt time.Time `bun:"created_at,notnull"` - UpdatedAt time.Time `bun:"updated_at,notnull"` + CreatedAt time.Time `bun:"created_at,type:timestamp,notnull"` + UpdatedAt time.Time `bun:"updated_at,type:timestamp,notnull"` } func NewBlob(props Property, repoID uuid.UUID, checkSum hash.Hash, size int64) (*Blob, error) { @@ -154,8 +154,8 @@ type TreeNode struct { SubObjects []TreeEntry `bun:"sub_objects,type:jsonb" json:"sub_objects"` Properties Property `bun:"properties,type:jsonb,notnull" json:"properties"` - CreatedAt time.Time `bun:"created_at,notnull" json:"created_at"` - UpdatedAt time.Time `bun:"updated_at,notnull" json:"updated_at"` + CreatedAt time.Time `bun:"created_at,type:timestamp,notnull" json:"created_at"` + UpdatedAt time.Time `bun:"updated_at,type:timestamp,notnull" json:"updated_at"` } func NewTreeNode(props Property, repoID uuid.UUID, subObjects ...TreeEntry) (*TreeNode, error) { diff --git a/models/user.go b/models/user.go index b51109fa..9e2a3952 100644 --- a/models/user.go +++ b/models/user.go @@ -14,12 +14,12 @@ type User struct { Name string `bun:"name,unique,notnull" json:"name"` Email string `bun:"email,unique,notnull" json:"email"` EncryptedPassword string `bun:"encrypted_password,notnull" json:"encrypted_password"` - CurrentSignInAt time.Time `bun:"current_sign_in_at" json:"current_sign_in_at"` - LastSignInAt time.Time `bun:"last_sign_in_at" json:"last_sign_in_at"` + CurrentSignInAt time.Time `bun:"current_sign_in_at,type:timestamp" json:"current_sign_in_at"` + LastSignInAt time.Time `bun:"last_sign_in_at,type:timestamp" json:"last_sign_in_at"` CurrentSignInIP string `bun:"current_sign_in_ip" json:"current_sign_in_ip"` LastSignInIP string `bun:"last_sign_in_ip" json:"last_sign_in_ip"` - CreatedAt time.Time `bun:"created_at,notnull" json:"created_at"` - UpdatedAt time.Time `bun:"updated_at,notnull" json:"updated_at"` + CreatedAt time.Time `bun:"created_at,type:timestamp,notnull" json:"created_at"` + UpdatedAt time.Time `bun:"updated_at,type:timestamp,notnull" json:"updated_at"` } type GetUserParams struct { diff --git a/models/wip.go b/models/wip.go index dc2e049d..4c4f127f 100644 --- a/models/wip.go +++ b/models/wip.go @@ -25,8 +25,8 @@ type WorkingInProcess struct { RefID uuid.UUID `bun:"ref_id,unique:creator_id_repository_id_ref_id_unique,type:uuid,notnull" json:"ref_id"` State WipState `bun:"state,notnull" json:"state"` CreatorID uuid.UUID `bun:"creator_id,unique:creator_id_repository_id_ref_id_unique,type:uuid,notnull" json:"creator_id"` - CreatedAt time.Time `bun:"created_at" json:"created_at"` - UpdatedAt time.Time `bun:"updated_at" json:"updated_at"` + CreatedAt time.Time `bun:"created_at,type:timestamp,notnull" json:"created_at"` + UpdatedAt time.Time `bun:"updated_at,type:timestamp,notnull" json:"updated_at"` } type GetWipParams struct { diff --git a/utils/pagination.go b/utils/pagination.go index 08190d4d..c7bbf293 100644 --- a/utils/pagination.go +++ b/utils/pagination.go @@ -2,6 +2,7 @@ package utils import ( "reflect" + "strconv" "time" ) @@ -32,8 +33,10 @@ func PaginationFor(hasMore bool, results interface{}, fieldName string) PageMana if token.Type() == reflect.TypeOf(time.Time{}) { pagination.NextOffset = token.Interface().(time.Time).String() } + case reflect.Int64: + pagination.NextOffset = strconv.FormatInt(token.Int(), 10) case reflect.String: - pagination.NextOffset = token.Interface().(string) + pagination.NextOffset = token.String() } return pagination } From 9a68869ae7b0a9becf80de53f601d9dec740c1c5 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Wed, 10 Jan 2024 17:21:01 +0800 Subject: [PATCH 142/210] chores: update readme and add license --- LICENSE-APACHE | 5 +++++ LICENSE-MIT | 19 +++++++++++++++++++ README.md | 29 ++++++++++++++++++++++++----- 3 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 00000000..14478a3b --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,5 @@ +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/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 00000000..72dc60d8 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index 88d3ecee..818a85e7 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,36 @@ # JiaoziFS A version control file system for data centric applications & teams. -## quick start +

+ + + +
+

-build +### Basic Build And Usage + +#### Requirement +1. To build JiaoziFS, you need a working installation of [Go 1.20.10 or higher](https://golang.org/dl/) +2. JiaoziFS use postgres to store running data, you can install at [postgres install installation guide](https://www.postgresql.org/docs/current/installation.html) + +#### Build And Running + +1. clone and build ```bash git clone https://github.com/jiaozifs/jiaozifs.git +cd jiaozifs make build ``` -init and running -```bash -./jzfs init --db postgres://li:li123@localhost:5432/jiaozifs?sslmode=disable +After following the above steps, you should be able to see an executable file named "jzfs." +2. init program and running +```bash +./jzfs init --db postgres://:@localhost:5432/jiaozifs?sslmode=disable ./jzfs daemon ``` + +## License + +Dual-licensed under [MIT](https://github.com/jiaozifs/jiaozifs/blob/main/LICENSE-MIT) + [Apache 2.0](https://github.com/jiaozifs/jiaozifs/blob/main/LICENSE-APACHE) From 5ac70cfbab49231cd635d803a2c6c068a4c8f48e Mon Sep 17 00:00:00 2001 From: Joy <54040689+Brownjy@users.noreply.github.com> Date: Thu, 11 Jan 2024 10:15:34 +0800 Subject: [PATCH 143/210] feat: delete repo data (#76) * feat: delete repo data --------- Co-authored-by: brown Co-authored-by: hunjixin <1084400399@qq.com> --- block/adapter.go | 1 + block/azure/adapter.go | 39 ++++++++++++++++++++-- block/azure/adapter_test.go | 2 +- block/blocktest/adapter.go | 63 ++++++++++++++++++++++++++++++++++++ block/gs/adapter.go | 32 ++++++++++++++++++ block/gs/adapter_test.go | 2 +- block/local/adapter.go | 21 ++++++++++-- block/local/adapter_test.go | 6 ++-- block/local/walker.go | 2 +- block/mem/adapter.go | 12 +++++++ block/s3/adapter.go | 47 +++++++++++++++++++++++++++ block/s3/adapter_test.go | 2 +- block/transient/adapter.go | 5 +++ controller/repository_ctl.go | 15 +++++++++ models/repository_test.go | 4 +++ 15 files changed, 241 insertions(+), 12 deletions(-) diff --git a/block/adapter.go b/block/adapter.go index 654c8736..f87d1e62 100644 --- a/block/adapter.go +++ b/block/adapter.go @@ -139,6 +139,7 @@ type Adapter interface { GetRange(ctx context.Context, obj ObjectPointer, startPosition int64, endPosition int64) (io.ReadCloser, error) GetProperties(ctx context.Context, obj ObjectPointer) (Properties, error) Remove(ctx context.Context, obj ObjectPointer) error + RemoveNameSpace(ctx context.Context, storageNamespace string) error Copy(ctx context.Context, sourceObj, destinationObj ObjectPointer) error CreateMultiPartUpload(ctx context.Context, obj ObjectPointer, r *http.Request, opts CreateMultiPartUploadOpts) (*CreateMultiPartUploadResponse, error) UploadPart(ctx context.Context, obj ObjectPointer, sizeBytes int64, reader io.Reader, uploadID string, partNumber int) (*UploadPartResponse, error) diff --git a/block/azure/adapter.go b/block/azure/adapter.go index c522fa9c..a02c56be 100644 --- a/block/azure/adapter.go +++ b/block/azure/adapter.go @@ -9,8 +9,6 @@ import ( "strings" "time" - "github.com/jiaozifs/jiaozifs/utils/hash" - "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" @@ -19,6 +17,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas" "github.com/jiaozifs/jiaozifs/block" "github.com/jiaozifs/jiaozifs/block/params" + "github.com/jiaozifs/jiaozifs/utils" + "github.com/jiaozifs/jiaozifs/utils/hash" ) const ( @@ -152,6 +152,7 @@ func (a *Adapter) Put(ctx context.Context, obj block.ObjectPointer, sizeBytes in if err != nil { return err } + _, err = containerClient.NewBlockBlobClient(qualifiedKey.BlobURL).UploadStream(ctx, reader, &azblob.UploadStreamOptions{}) return err } @@ -350,6 +351,40 @@ func (a *Adapter) Remove(ctx context.Context, obj block.ObjectPointer) error { return err } +func (a *Adapter) RemoveNameSpace(ctx context.Context, namespace string) error { + var err error + parsedNamespace, err := url.ParseRequestURI(namespace) + if err != nil { + return err + } + qp, err := ResolveBlobURLInfoFromURL(parsedNamespace) + if err != nil { + return err + } + containerClient, err := a.clientCache.NewContainerClient(qp.StorageAccountName, qp.ContainerName) + if err != nil { + return err + } + objs := containerClient.NewListBlobsFlatPager(&azblob.ListBlobsFlatOptions{ + Prefix: &qp.BlobURL, + }) + for objs.More() { + page, err := objs.NextPage(ctx) + if err != nil { + return err + } + for _, blob := range page.ListBlobsFlatSegmentResponse.Segment.BlobItems { + blobClient := containerClient.NewBlobClient(utils.StringValue(blob.Name)) + _, err = blobClient.Delete(ctx, &azblob.DeleteBlobOptions{}) + if err != nil { + return err + } + } + } + + return err +} + func (a *Adapter) Copy(ctx context.Context, sourceObj, destinationObj block.ObjectPointer) error { var err error defer reportMetrics("Copy", time.Now(), nil, &err) diff --git a/block/azure/adapter_test.go b/block/azure/adapter_test.go index f9c7eeb2..492808c8 100644 --- a/block/azure/adapter_test.go +++ b/block/azure/adapter_test.go @@ -15,7 +15,7 @@ import ( func TestAzureAdapter(t *testing.T) { basePath, err := url.JoinPath(blockURL, containerName) require.NoError(t, err) - localPath, err := url.JoinPath(basePath, "lakefs") + localPath, err := url.JoinPath(basePath, "jiaozfs") require.NoError(t, err) externalPath, err := url.JoinPath(basePath, "external") require.NoError(t, err) diff --git a/block/blocktest/adapter.go b/block/blocktest/adapter.go index 8140762f..5ff1ba48 100644 --- a/block/blocktest/adapter.go +++ b/block/blocktest/adapter.go @@ -26,6 +26,69 @@ func AdapterTest(t *testing.T, adapter block.Adapter, storageNamespace, external t.Run("Adapter_Exists", func(t *testing.T) { testAdapterExists(t, adapter, storageNamespace) }) t.Run("Adapter_GetRange", func(t *testing.T) { testAdapterGetRange(t, adapter, storageNamespace) }) t.Run("Adapter_Walker", func(t *testing.T) { testAdapterWalker(t, adapter, storageNamespace) }) + t.Run("Adapter_Clean", func(t *testing.T) { testAdapterClean(t, adapter, storageNamespace) }) +} + +func testAdapterClean(t *testing.T, adapter block.Adapter, storageNamespace string) { //nolint + ctx := context.Background() + const content = "content used for testing" + + tests := []struct { + name string + additionalObjects []string + path string + wantErr bool + wantTree []string + }{ + { + name: "test_single", + path: "README", + wantErr: false, + wantTree: []string{}, + }, + + { + name: "test_under_folder", + path: "src/tools.go", + wantErr: false, + wantTree: []string{}, + }, + { + name: "test_under_multiple_folders", + path: "a/b/c/d.txt", + wantErr: false, + wantTree: []string{}, + }, + { + name: "file_in_the_way", + path: "a/b/c/d.txt", + additionalObjects: []string{"a/b/blocker.txt"}, + wantErr: false, + wantTree: []string{"/a/b/blocker.txt"}, + }, + } + + // setup env + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + envObjects := tt.additionalObjects + envObjects = append(envObjects, tt.path) + for _, p := range envObjects { + obj := block.ObjectPointer{ + StorageNamespace: storageNamespace, + Identifier: tt.name + "/" + p, + IdentifierType: block.IdentifierTypeRelative, + } + require.NoError(t, adapter.Put(ctx, obj, int64(len(content)), strings.NewReader(content), block.PutOpts{})) + } + }) + } + + // clean + t.Run("clean repo", func(t *testing.T) { + err := adapter.RemoveNameSpace(ctx, storageNamespace) + require.NoError(t, err) + }) } func testAdapterPutGet(t *testing.T, adapter block.Adapter, storageNamespace, externalPath string) { diff --git a/block/gs/adapter.go b/block/gs/adapter.go index d19ebecc..17ef37ff 100644 --- a/block/gs/adapter.go +++ b/block/gs/adapter.go @@ -225,6 +225,38 @@ func (a *Adapter) Remove(ctx context.Context, obj block.ObjectPointer) error { return nil } +func (a *Adapter) RemoveNameSpace(ctx context.Context, namespace string) error { + var err error + defer reportMetrics("RemoveNameSpace", time.Now(), nil, &err) + bucket, key, err := a.extractParamsFromObj(block.ObjectPointer{ + StorageNamespace: namespace, + Identifier: "'", + IdentifierType: block.IdentifierTypeRelative, + }) + if err != nil { + return err + } + iter := a.client.Bucket(bucket).Objects(ctx, &storage.Query{ + Delimiter: key, + }) + + for { + obj, err := iter.Next() + if err != nil { + return err + } + if iter.PageInfo().Remaining() == 0 { + break + } + err = a.client.Bucket(bucket).Object(obj.Name).Delete(ctx) + if err != nil { + return fmt.Errorf("Object(%q).Delete: %w", key, err) + } + } + + return nil +} + func (a *Adapter) Copy(ctx context.Context, sourceObj, destinationObj block.ObjectPointer) error { var err error defer reportMetrics("Copy", time.Now(), nil, &err) diff --git a/block/gs/adapter_test.go b/block/gs/adapter_test.go index a73ac35c..bbab1e74 100644 --- a/block/gs/adapter_test.go +++ b/block/gs/adapter_test.go @@ -17,7 +17,7 @@ func newAdapter() *gs.Adapter { func TestAdapter(t *testing.T) { basePath, err := url.JoinPath("gs://", bucketName) require.NoError(t, err) - localPath, err := url.JoinPath(basePath, "lakefs") + localPath, err := url.JoinPath(basePath, "jiaozfs") require.NoError(t, err) externalPath, err := url.JoinPath(basePath, "external") require.NoError(t, err) diff --git a/block/local/adapter.go b/block/local/adapter.go index 71589dce..66e1fa57 100644 --- a/block/local/adapter.go +++ b/block/local/adapter.go @@ -35,9 +35,10 @@ type Adapter struct { } var ( - ErrPathNotWritable = errors.New("path provided is not writable") - ErrInvalidUploadIDFormat = errors.New("invalid upload id format") - ErrBadPath = errors.New("bad path traversal blocked") + ErrPathNotWritable = errors.New("path provided is not writable") + ErrInvalidUploadIDFormat = errors.New("invalid upload id format") + ErrBadPath = errors.New("bad path traversal blocked") + ErrInvalidStorageNamespace = errors.New("invalid storageNamespace") ) type QualifiedKey struct { @@ -156,6 +157,7 @@ func (l *Adapter) Path() string { func (l *Adapter) Put(_ context.Context, obj block.ObjectPointer, _ int64, reader io.Reader, _ block.PutOpts) error { p, err := l.extractParamsFromObj(obj) + fmt.Println(p) if err != nil { return err } @@ -189,6 +191,19 @@ func (l *Adapter) Remove(_ context.Context, obj block.ObjectPointer) error { return nil } +func (l *Adapter) RemoveNameSpace(_ context.Context, storageNamespace string) error { + p, err := l.extractParamsFromObj(block.ObjectPointer{ + StorageNamespace: storageNamespace, + Identifier: "", + IdentifierType: block.IdentifierTypeRelative, + }) + if err != nil { + return err + } + p = filepath.Clean(p) + return os.RemoveAll(p) +} + func removeEmptyDirUntil(dir string, stopAt string) { if stopAt == "" { return diff --git a/block/local/adapter_test.go b/block/local/adapter_test.go index a193ddcc..84a8ee63 100644 --- a/block/local/adapter_test.go +++ b/block/local/adapter_test.go @@ -15,8 +15,8 @@ const testStorageNamespace = "local://test" func TestLocalAdapter(t *testing.T) { tmpDir := t.TempDir() - localPath := path.Join(tmpDir, "lakefs") - externalPath := block.BlockstoreTypeLocal + "://" + path.Join(tmpDir, "lakefs", "external") + localPath := path.Join(tmpDir, "jiaozfs") + externalPath := block.BlockstoreTypeLocal + "://" + path.Join(tmpDir, "jiaozfs", "external") adapter, err := local.NewAdapter(localPath, local.WithRemoveEmptyDir(false)) if err != nil { t.Fatal("Failed to create new adapter", err) @@ -26,7 +26,7 @@ func TestLocalAdapter(t *testing.T) { func TestAdapterNamespace(t *testing.T) { tmpDir := t.TempDir() - localPath := path.Join(tmpDir, "lakefs") + localPath := path.Join(tmpDir, "jiaozfs") adapter, err := local.NewAdapter(localPath, local.WithRemoveEmptyDir(false)) require.NoError(t, err, "create new adapter") expr, err := regexp.Compile(adapter.GetStorageNamespaceInfo().ValidityRegex) diff --git a/block/local/walker.go b/block/local/walker.go index 85b9ab89..f33ef74f 100644 --- a/block/local/walker.go +++ b/block/local/walker.go @@ -19,7 +19,7 @@ import ( gonanoid "github.com/matoous/go-nanoid/v2" ) -const cacheDirName = "_lakefs_cache" +const cacheDirName = "_jiaozfs_cache" type Walker struct { mark block.Mark diff --git a/block/mem/adapter.go b/block/mem/adapter.go index e7028864..edf8dfa2 100644 --- a/block/mem/adapter.go +++ b/block/mem/adapter.go @@ -176,6 +176,18 @@ func (a *Adapter) Remove(_ context.Context, obj block.ObjectPointer) error { return nil } +func (a *Adapter) RemoveNameSpace(_ context.Context, storageNamespace string) error { + if storageNamespace == "" { + return fmt.Errorf("storageNamespace cannot be empty") + } + for key := range a.data { + if strings.HasPrefix(key, storageNamespace+":") { + delete(a.data, key) + } + } + return nil +} + func (a *Adapter) Copy(_ context.Context, sourceObj, destinationObj block.ObjectPointer) error { if err := verifyObjectPointer(sourceObj); err != nil { return err diff --git a/block/s3/adapter.go b/block/s3/adapter.go index b412d2af..c9980285 100644 --- a/block/s3/adapter.go +++ b/block/s3/adapter.go @@ -12,6 +12,8 @@ import ( "sync/atomic" "time" + "github.com/jiaozifs/jiaozifs/utils" + "github.com/aws/aws-sdk-go-v2/aws" v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" "github.com/aws/aws-sdk-go-v2/config" @@ -511,6 +513,51 @@ func (a *Adapter) Remove(ctx context.Context, obj block.ObjectPointer) error { return waiter.Wait(ctx, headInput, maxWaitDur) } +func (a *Adapter) RemoveNameSpace(ctx context.Context, namespace string) error { + var err error + defer reportMetrics("RemoveNameSpace", time.Now(), nil, &err) + bucket, key, _, err := a.extractParamsFromObj(block.ObjectPointer{ + StorageNamespace: namespace, + Identifier: "", + IdentifierType: block.IdentifierTypeRelative, + }) + if err != nil { + return err + } + + for { + listInput := &s3.ListObjectsInput{ + Bucket: aws.String(bucket), + Prefix: utils.String(key), + MaxKeys: utils.Int32(500), + } + client := a.clients.Get(ctx, bucket) + resp, err := client.ListObjects(ctx, listInput) + if err != nil { + log.Errorf("failed to list S3 object %v", err) + return err + } + + for _, obj := range resp.Contents { + deleteInput := &s3.DeleteObjectInput{ + Bucket: aws.String(bucket), + Key: obj.Key, + } + client := a.clients.Get(ctx, bucket) + _, err = client.DeleteObject(ctx, deleteInput) + if err != nil { + log.Errorf("failed to delete S3 object %v", err) + return err + } + } + if len(resp.Contents) < 500 { + break + } + } + + return nil +} + func (a *Adapter) copyPart(ctx context.Context, sourceObj, destinationObj block.ObjectPointer, uploadID string, partNumber int, byteRange *string) (*block.UploadPartResponse, error) { srcKey, err := resolveNamespace(sourceObj) if err != nil { diff --git a/block/s3/adapter_test.go b/block/s3/adapter_test.go index 6a368dae..e50853b9 100644 --- a/block/s3/adapter_test.go +++ b/block/s3/adapter_test.go @@ -33,7 +33,7 @@ func getS3BlockAdapter(t *testing.T) *s3.Adapter { func TestS3Adapter(t *testing.T) { basePath, err := url.JoinPath("s3://", bucketName) require.NoError(t, err) - localPath, err := url.JoinPath(basePath, "lakefs") + localPath, err := url.JoinPath(basePath, "jiaozfs") require.NoError(t, err) externalPath, err := url.JoinPath(basePath, "external") require.NoError(t, err) diff --git a/block/transient/adapter.go b/block/transient/adapter.go index 090bf0a2..0d7497bf 100644 --- a/block/transient/adapter.go +++ b/block/transient/adapter.go @@ -64,6 +64,11 @@ func (a *Adapter) Remove(_ context.Context, _ block.ObjectPointer) error { return nil } +func (a *Adapter) RemoveNameSpace(_ context.Context, _ string) error { + //TODO implement me + panic("implement me") +} + func (a *Adapter) Copy(_ context.Context, _, _ block.ObjectPointer) error { return nil } diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index ec31a598..fd382952 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -14,6 +14,7 @@ import ( logging "github.com/ipfs/go-log/v2" "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/auth" + "github.com/jiaozifs/jiaozifs/block/factory" "github.com/jiaozifs/jiaozifs/block/params" "github.com/jiaozifs/jiaozifs/config" "github.com/jiaozifs/jiaozifs/models" @@ -295,6 +296,20 @@ func (repositoryCtl RepositoryController) DeleteRepository(ctx context.Context, return } + //clean repo data + if repository.UsePublicStorage { //todo for use custom storage, maybe add a config in setting or params in delete repository api + adapter, err := factory.BuildBlockAdapter(ctx, repositoryCtl.PublicStorageConfig) + if err != nil { + w.Error(err) + return + } + err = adapter.RemoveNameSpace(ctx, *repository.StorageNamespace) + if err != nil { + w.Error(err) + return + } + } + w.OK() } diff --git a/models/repository_test.go b/models/repository_test.go index 0b0e12cb..810ed297 100644 --- a/models/repository_test.go +++ b/models/repository_test.go @@ -149,4 +149,8 @@ func TestRepositoryRepoInsert(t *testing.T) { _, err = repo.Get(ctx, models.NewGetRepoParams().SetID(secRepo.ID)) require.ErrorIs(t, err, models.ErrNotFound) + + affectRows, err = repo.Delete(ctx, deleteParams) + require.NoError(t, err) + require.Equal(t, int64(0), affectRows) } From be7bcb181b01a7ba987eba167ebc84f3e16588ac Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Thu, 11 Jan 2024 17:44:41 +0800 Subject: [PATCH 144/210] fix: change commit reverse --- integrationtest/commit_test.go | 29 ++++++++++++++++++++++++---- integrationtest/helper_test.go | 6 +++--- utils/httputil/client.go | 14 -------------- versionmgr/work_repo.go | 35 ++++++++++++++++++++++++++++------ 4 files changed, 57 insertions(+), 27 deletions(-) delete mode 100644 utils/httputil/client.go diff --git a/integrationtest/commit_test.go b/integrationtest/commit_test.go index 65bbc538..3860bb3b 100644 --- a/integrationtest/commit_test.go +++ b/integrationtest/commit_test.go @@ -2,6 +2,7 @@ package integrationtest import ( "context" + "fmt" "net/http" "github.com/jiaozifs/jiaozifs/utils/hash" @@ -382,13 +383,21 @@ func GetCommitChangesSpec(ctx context.Context, urlStr string) func(c convey.C) { loginAndSwitch(ctx, c, client, "getCommitChanges login", userName, false) createRepo(ctx, c, client, repoName) createWip(ctx, c, client, "feat get entries test0", userName, repoName, "main") - uploadObject(ctx, c, client, "update f1 to test branch", userName, repoName, "main", "m.dat") + uploadObject(ctx, c, client, "update m.dat to test branch", userName, repoName, "main", "m.dat") commitWip(ctx, c, client, "commit kelly first changes", userName, repoName, "main", "test") - uploadObject(ctx, c, client, "update f2 to test branch", userName, repoName, "main", "g/x.dat") + uploadObject(ctx, c, client, "update g/x.dat to test branch", userName, repoName, "main", "g/x.dat") commitWip(ctx, c, client, "commit kelly second changes", userName, repoName, "main", "test") - uploadObject(ctx, c, client, "update f3 to test branch", userName, repoName, "main", "g/m.dat") + //delete + deleteObject(ctx, c, client, "delete g/x.dat", userName, repoName, "main", "g/x.dat") + + //modify + deleteObject(ctx, c, client, "delete m.dat", userName, repoName, "main", "m.dat") + uploadObject(ctx, c, client, "update m.dat to test branch again", userName, repoName, "main", "m.dat") + + //insert + uploadObject(ctx, c, client, "update g/m.dat to test branch", userName, repoName, "main", "g/m.dat") commitWip(ctx, c, client, "commit kelly third changes", userName, repoName, "main", "test") c.Convey("get commit change", func(c convey.C) { @@ -446,6 +455,9 @@ func GetCommitChangesSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("success to get first changes", func() { + fmt.Println(commits[0].Hash) + fmt.Println(commits[1].Hash) + fmt.Println(commits[2].Hash) resp, err := client.GetCommitChanges(ctx, userName, repoName, commits[0].Hash, &api.GetCommitChangesParams{ Path: utils.String("/"), }) @@ -454,7 +466,16 @@ func GetCommitChangesSpec(ctx context.Context, urlStr string) func(c convey.C) { result, err := api.ParseCompareCommitResponse(resp) convey.So(err, convey.ShouldBeNil) - convey.So(*result.JSON200, convey.ShouldHaveLength, 1) + convey.So(*result.JSON200, convey.ShouldHaveLength, 3) + + convey.So((*result.JSON200)[0].Path, convey.ShouldEqual, "g/m.dat") + convey.So((*result.JSON200)[0].Action, convey.ShouldEqual, api.N1) + + convey.So((*result.JSON200)[1].Path, convey.ShouldEqual, "g/x.dat") + convey.So((*result.JSON200)[1].Action, convey.ShouldEqual, api.N2) + + convey.So((*result.JSON200)[2].Path, convey.ShouldEqual, "m.dat") + convey.So((*result.JSON200)[2].Action, convey.ShouldEqual, api.N3) }) c.Convey("success to get first commit changes", func() { diff --git a/integrationtest/helper_test.go b/integrationtest/helper_test.go index b3ca06ad..2817c5fb 100644 --- a/integrationtest/helper_test.go +++ b/integrationtest/helper_test.go @@ -177,12 +177,12 @@ func uploadObject(ctx context.Context, c convey.C, client *api.Client, title str func deleteObject(ctx context.Context, c convey.C, client *api.Client, title string, user string, repoName string, refName string, path string) { //nolint c.Convey("upload object "+title, func(c convey.C) { c.Convey("success upload object", func() { - resp, err := client.UploadObjectWithBody(ctx, user, repoName, &api.UploadObjectParams{ + resp, err := client.DeleteObject(ctx, user, repoName, &api.DeleteObjectParams{ RefName: refName, Path: path, - }, "application/octet-stream", io.LimitReader(rand.Reader, 50)) + }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) }) }) } diff --git a/utils/httputil/client.go b/utils/httputil/client.go deleted file mode 100644 index 0c78fe32..00000000 --- a/utils/httputil/client.go +++ /dev/null @@ -1,14 +0,0 @@ -package httputil - -import "net/http" - -// GetRequestLakeFSClient get lakeFS client identifier from request. -// -// It extracts the data from X-Lakefs-Client header and fallback to the user-agent -func GetRequestLakeFSClient(r *http.Request) string { - id := r.Header.Get("X-Lakefs-Client") - if id == "" { - id = r.UserAgent() - } - return id -} diff --git a/versionmgr/work_repo.go b/versionmgr/work_repo.go index ca51b101..492c26cc 100644 --- a/versionmgr/work_repo.go +++ b/versionmgr/work_repo.go @@ -582,16 +582,39 @@ func (repository *WorkRepository) DiffCommit(ctx context.Context, toCommitID has } func (repository *WorkRepository) GetCommitChanges(ctx context.Context, pathPrefix string) (*Changes, error) { - if len(repository.commit.ParentHashes) == 0 { - workTree, err := repository.RootTree(ctx) + commitHash := hash.Empty + if len(repository.commit.ParentHashes) == 1 { + commitHash = repository.commit.ParentHashes[0] + } else if len(repository.commit.ParentHashes) == 2 { + commitHash = repository.commit.ParentHashes[1] + } + + treeHash := hash.Empty + if !commitHash.IsEmpty() { + commit, err := repository.repo.CommitRepo(repository.repoModel.ID).Commit(ctx, commitHash) + if err != nil { + return nil, err + } + treeHash = commit.TreeHash + } + + workTree, err := NewWorkTree(ctx, repository.repo.FileTreeRepo(repository.repoModel.ID), models.NewRootTreeEntry(treeHash)) + if err != nil { + return nil, err + } + files, _ := workTree.Ls(ctx, "g") + fmt.Println(files) + { + workTree2, err := NewWorkTree(ctx, repository.repo.FileTreeRepo(repository.repoModel.ID), models.NewRootTreeEntry(repository.commit.TreeHash)) if err != nil { return nil, err } - return workTree.Diff(ctx, hash.Empty, pathPrefix) - } else if len(repository.commit.ParentHashes) == 1 { - return repository.DiffCommit(ctx, repository.commit.ParentHashes[0], pathPrefix) + f2, _ := workTree2.Ls(ctx, "g") + fmt.Println(f2) } - return repository.DiffCommit(ctx, repository.commit.ParentHashes[1], pathPrefix) + fmt.Println(repository.commit.Hash.Hex()) + fmt.Println(commitHash.Hex()) + return workTree.Diff(ctx, repository.commit.TreeHash, pathPrefix) } func (repository *WorkRepository) GetMergeState(ctx context.Context, toMergeCommitHash hash.Hash) ([]*ChangePair, error) { From 7b9f1faa29c50973df19f67f6aa57ad9e55bd6b6 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Sat, 13 Jan 2024 20:40:46 +0800 Subject: [PATCH 145/210] feat: remove wips belong to branch which delete --- integrationtest/branch_test.go | 1 + versionmgr/work_repo.go | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/integrationtest/branch_test.go b/integrationtest/branch_test.go index 384cf1f2..33d7965f 100644 --- a/integrationtest/branch_test.go +++ b/integrationtest/branch_test.go @@ -257,6 +257,7 @@ func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) }) + createWip(ctx, c, client, "creat wip for delete", userName, repoName, "feat/sec_branch") c.Convey("delete branch successful", func() { resp, err := client.DeleteBranch(ctx, userName, repoName, &api.DeleteBranchParams{RefName: "feat/sec_branch"}) convey.So(err, convey.ShouldBeNil) diff --git a/versionmgr/work_repo.go b/versionmgr/work_repo.go index 492c26cc..a17e450b 100644 --- a/versionmgr/work_repo.go +++ b/versionmgr/work_repo.go @@ -509,16 +509,31 @@ func (repository *WorkRepository) CreateBranch(ctx context.Context, branchName s // DeleteBranch delete branch also delete wip belong this branch func (repository *WorkRepository) DeleteBranch(ctx context.Context) error { return repository.repo.Transaction(ctx, func(repo models.IRepo) error { + wips, err := repo.WipRepo().List(ctx, models.NewListWipParams().SetRepositoryID(repository.repoModel.ID).SetRefID(repository.branch.ID)) + if err != nil { + return err + } + for _, wip := range wips { + _, err = repo.WipRepo().Delete(ctx, models.NewDeleteWipParams().SetID(wip.ID)) + if err != nil { + return err + } + } + deleteBranchParams := models.NewDeleteBranchParams(). SetRepositoryID(repository.repoModel.ID). SetName(repository.branch.Name) - _, err := repo.BranchRepo().Delete(ctx, deleteBranchParams) + _, err = repo.BranchRepo().Delete(ctx, deleteBranchParams) if err != nil { return err } deleteWipParams := models.NewDeleteWipParams().SetRepositoryID(repository.repoModel.ID).SetRefID(repository.branch.ID) _, err = repo.WipRepo().Delete(ctx, deleteWipParams) + if err != nil { + return err + } + return err }) } From 0fd7db2ff0c24606d6f464d337198149abaa11ae Mon Sep 17 00:00:00 2001 From: zjy Date: Mon, 15 Jan 2024 09:09:36 +0800 Subject: [PATCH 146/210] feat: add name constraint --- controller/branch_ctl.go | 6 +++--- controller/object_ctl.go | 15 +++++++++++++++ controller/repository_ctl.go | 8 ++------ controller/user_ctl.go | 18 +++++++++++++++++- integrationtest/objects_test.go | 9 +++++++++ integrationtest/user_test.go | 12 ++++++++++++ 6 files changed, 58 insertions(+), 10 deletions(-) diff --git a/controller/branch_ctl.go b/controller/branch_ctl.go index f21677c5..a1d016a7 100644 --- a/controller/branch_ctl.go +++ b/controller/branch_ctl.go @@ -20,7 +20,7 @@ import ( ) var MaxBranchNameLength = 40 -var branchNameRegex = regexp.MustCompile("^[a-zA-Z0-9_]*$") +var branchNameRegex = regexp.MustCompile(`^\w[-\w]*$`) func CheckBranchName(name string) error { for _, blackName := range RepoNameBlackList { @@ -39,11 +39,11 @@ func CheckBranchName(name string) error { } if !branchNameRegex.Match([]byte(seg[0])) { - return fmt.Errorf("branch name must be combination of number and letter or combine with '/'") + return fmt.Errorf("invalid branch name: %s, must start with a number or letter and can only contain numbers, letters, hyphens or underscores", seg[0]) } if len(seg) > 2 { if !branchNameRegex.Match([]byte(seg[1])) { - return fmt.Errorf("branch name must be combination of number and letter or combine with '/'") + return fmt.Errorf("invalid branch name: %s, must start with a number or letter and can only contain numbers, letters, hyphens or underscores", seg[1]) } } return nil diff --git a/controller/object_ctl.go b/controller/object_ctl.go index 76056bc5..4173f120 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -8,6 +8,7 @@ import ( "mime" "mime/multipart" "net/http" + "regexp" "time" "github.com/go-openapi/swag" @@ -25,6 +26,14 @@ import ( ) var objLog = logging.Logger("object_ctl") +var pathRegex = regexp.MustCompile(`^([a-zA-Z0-9]+\/)*[a-zA-Z0-9]+\.[a-zA-Z0-9]+$`) //nolint + +func CheckObjectPath(path string) error { + if !pathRegex.MatchString(path) { + return fmt.Errorf("invalid object path: it must start with a letter or digit, can contain letters, digits, and must be separated by slashes") + } + return nil +} type ObjectController struct { fx.In @@ -312,6 +321,12 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes } defer reader.Close() //nolint + err = CheckObjectPath(params.Path) + if err != nil { + w.BadRequest(err.Error()) + return + } + operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index ec31a598..13422217 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -26,8 +26,7 @@ import ( const DefaultBranchName = "main" var repoLog = logging.Logger("repo control") -var maxNameLength = 20 -var alphanumeric = regexp.MustCompile("^[a-zA-Z0-9_]*$") +var alphanumeric = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]$`) // RepoNameBlackList forbid repo name, reserve for routes var RepoNameBlackList = []string{"repository", "repositories", "wip", "wips", "object", "objects", "commit", "commits", "ref", "refs", "repo", "repos", "user", "users"} @@ -40,10 +39,7 @@ func CheckRepositoryName(name string) error { } if !alphanumeric.MatchString(name) { - return errors.New("repository name must be combination of number and letter") - } - if len(name) > maxNameLength { - return errors.New("repository name is too long") + return errors.New("repository name must start with a number or letter, can only contain numbers, letters, or hyphens, and must be between 3 and 63 characters in length") } return nil } diff --git a/controller/user_ctl.go b/controller/user_ctl.go index 1a4f842c..8571b1d7 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -3,7 +3,9 @@ package controller import ( "context" "encoding/hex" + "errors" "net/http" + "regexp" "time" "github.com/jiaozifs/jiaozifs/utils" @@ -21,11 +23,19 @@ import ( ) var userCtlLog = logging.Logger("user_ctl") +var usernameRegex = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_-]{1,28}[a-zA-Z0-9]$`) const ( AuthHeader = "Authorization" ) +func CheckUserName(name string) error { + if !usernameRegex.MatchString(name) { + return errors.New("invalid username: it must start and end with a letter or digit, can contain letters, digits, hyphens, and cannot start or end with a hyphen; the length must be between 3 and 30 characters") + } + return nil +} + type UserController struct { fx.In @@ -80,6 +90,12 @@ func (userCtl UserController) Login(ctx context.Context, w *api.JiaozifsResponse } func (userCtl UserController) Register(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.RegisterJSONRequestBody) { + err := CheckUserName(body.Name) + if err != nil { + w.BadRequest(err.Error()) + return + } + register := auth.Register{ Username: body.Name, Email: string(body.Email), @@ -87,7 +103,7 @@ func (userCtl UserController) Register(ctx context.Context, w *api.JiaozifsRespo } // perform register - err := register.Register(ctx, userCtl.Repo.UserRepo()) + err = register.Register(ctx, userCtl.Repo.UserRepo()) if err != nil { w.Error(err) return diff --git a/integrationtest/objects_test.go b/integrationtest/objects_test.go index a4c71f8f..6b149c7f 100644 --- a/integrationtest/objects_test.go +++ b/integrationtest/objects_test.go @@ -93,6 +93,15 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) }) + c.Convey("invalid path", func() { + resp, err := client.UploadObjectWithBody(ctx, userName, repoName, &api.UploadObjectParams{ + RefName: branchName, + Path: "a~a.bin", + }, "application/octet-stream", bytes.NewReader([]byte{1, 2, 3, 4, 5, 6, 7, 8})) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + c.Convey("success upload object", func() { resp, err := client.UploadObjectWithBody(ctx, userName, repoName, &api.UploadObjectParams{ RefName: branchName, diff --git a/integrationtest/user_test.go b/integrationtest/user_test.go index c2392310..381d5358 100644 --- a/integrationtest/user_test.go +++ b/integrationtest/user_test.go @@ -2,6 +2,8 @@ package integrationtest import ( "context" + "fmt" + openapi_types "github.com/oapi-codegen/runtime/types" "net/http" "github.com/jiaozifs/jiaozifs/api" @@ -15,6 +17,16 @@ func UserSpec(ctx context.Context, urlStr string) func(c convey.C) { userName := "admin" createUser(ctx, c, client, userName) + c.Convey("invalid username", func() { + resp, err := client.Register(ctx, api.RegisterJSONRequestBody{ + Name: "admin!@#", + Password: "12345678", + Email: openapi_types.Email(fmt.Sprintf("mock%d@gmail.com", count)), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + c.Convey("usr profile no cookie", func() { resp, err := client.GetUserInfo(ctx) convey.So(err, convey.ShouldBeNil) From 87d5f961e0ef4882681d25a18565cbe20cc4d6b7 Mon Sep 17 00:00:00 2001 From: brown Date: Mon, 15 Jan 2024 10:13:42 +0800 Subject: [PATCH 147/210] fix: fix integrationtest --- controller/branch_ctl.go | 2 +- controller/repository_ctl.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/controller/branch_ctl.go b/controller/branch_ctl.go index a1d016a7..0515225e 100644 --- a/controller/branch_ctl.go +++ b/controller/branch_ctl.go @@ -20,7 +20,7 @@ import ( ) var MaxBranchNameLength = 40 -var branchNameRegex = regexp.MustCompile(`^\w[-\w]*$`) +var branchNameRegex = regexp.MustCompile(`^[\w]+\/?[\w]+$`) func CheckBranchName(name string) error { for _, blackName := range RepoNameBlackList { diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index e3d66773..91de9806 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -27,7 +27,7 @@ import ( const DefaultBranchName = "main" var repoLog = logging.Logger("repo control") -var alphanumeric = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]$`) +var alphanumeric = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_\-]{1,61}[a-zA-Z0-9]$`) // RepoNameBlackList forbid repo name, reserve for routes var RepoNameBlackList = []string{"repository", "repositories", "wip", "wips", "object", "objects", "commit", "commits", "ref", "refs", "repo", "repos", "user", "users"} From df0e7ca23b50fc34cd6d6ecad21b381e58ea6b4e Mon Sep 17 00:00:00 2001 From: brown Date: Mon, 15 Jan 2024 10:39:27 +0800 Subject: [PATCH 148/210] fix: fix object upload regular expression --- controller/object_ctl.go | 2 +- go.mod | 10 +++++----- go.sum | 32 ++++++++++++++++++++++++++++++++ integrationtest/objects_test.go | 9 +++++++++ integrationtest/user_test.go | 3 ++- 5 files changed, 49 insertions(+), 7 deletions(-) diff --git a/controller/object_ctl.go b/controller/object_ctl.go index 4173f120..dd027010 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -26,7 +26,7 @@ import ( ) var objLog = logging.Logger("object_ctl") -var pathRegex = regexp.MustCompile(`^([a-zA-Z0-9]+\/)*[a-zA-Z0-9]+\.[a-zA-Z0-9]+$`) //nolint +var pathRegex = regexp.MustCompile(`^([a-zA-Z0-9]+\/)*[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)?$`) //nolint func CheckObjectPath(path string) error { if !pathRegex.MatchString(path) { diff --git a/go.mod b/go.mod index 95b6090b..93f761ee 100644 --- a/go.mod +++ b/go.mod @@ -55,7 +55,7 @@ require ( github.com/uptrace/bun/extra/bundebug v1.1.16 go.uber.org/fx v1.20.1 go.uber.org/mock v0.3.0 - golang.org/x/crypto v0.16.0 + golang.org/x/crypto v0.18.0 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 golang.org/x/oauth2 v0.13.0 golang.org/x/text v0.14.0 @@ -172,10 +172,10 @@ require ( go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.23.0 // indirect golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.19.0 // indirect - golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/tools v0.16.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/tools v0.17.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect diff --git a/go.sum b/go.sum index 0b539cee..a3e64e06 100644 --- a/go.sum +++ b/go.sum @@ -124,18 +124,24 @@ github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -177,6 +183,7 @@ github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNIT github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= @@ -189,6 +196,7 @@ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9 github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= @@ -308,6 +316,7 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -315,6 +324,7 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= @@ -362,6 +372,7 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -371,6 +382,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oapi-codegen/runtime v1.1.0 h1:rJpoNUawn5XTvekgfkvSZr0RqEnoYpFkyvrzfWeFKWM= github.com/oapi-codegen/runtime v1.1.0/go.mod h1:BeSfBkWWWnAnGdyS+S/GnlbmHKzf8/hwkvelJZDeKA8= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -379,6 +392,8 @@ github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrB github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v1.1.7 h1:y2EZDS8sNng4Ksf0GUYNhKbTShZJPJg1FiXJNH/uoCk= github.com/opencontainers/runc v1.1.7/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= @@ -421,6 +436,8 @@ github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9c github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= @@ -455,6 +472,7 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/thanhpk/randstr v1.0.6 h1:psAOktJFD4vV9NEVb3qkhRSMvYh4ORRaj1+w/hn4B+o= github.com/thanhpk/randstr v1.0.6/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= @@ -471,6 +489,9 @@ github.com/uptrace/bun/driver/pgdriver v1.1.16 h1:b/NiSXk6Ldw7KLfMLbOqIkm4odHd7Q github.com/uptrace/bun/driver/pgdriver v1.1.16/go.mod h1:Rmfbc+7lx1z/umjMyAxkOHK81LgnGj71XC5YpA6k1vU= github.com/uptrace/bun/extra/bundebug v1.1.16 h1:SgicRQGtnjhrIhlYOxdkOm1Em4s6HykmT3JblHnoTBM= github.com/uptrace/bun/extra/bundebug v1.1.16/go.mod h1:SkiOkfUirBiO1Htc4s5bQKEq+JSeU1TkBVpMsPz2ePM= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= @@ -523,6 +544,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -549,6 +572,7 @@ golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPI golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -598,6 +622,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -622,6 +648,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -669,6 +697,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -735,6 +765,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/integrationtest/objects_test.go b/integrationtest/objects_test.go index 6b149c7f..587d9ad6 100644 --- a/integrationtest/objects_test.go +++ b/integrationtest/objects_test.go @@ -102,6 +102,15 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) }) + c.Convey("no sufix file", func() { + resp, err := client.UploadObjectWithBody(ctx, userName, repoName, &api.UploadObjectParams{ + RefName: branchName, + Path: "aaa", + }, "application/octet-stream", bytes.NewReader([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9})) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + }) + c.Convey("success upload object", func() { resp, err := client.UploadObjectWithBody(ctx, userName, repoName, &api.UploadObjectParams{ RefName: branchName, diff --git a/integrationtest/user_test.go b/integrationtest/user_test.go index 381d5358..9fe0e8a7 100644 --- a/integrationtest/user_test.go +++ b/integrationtest/user_test.go @@ -3,9 +3,10 @@ package integrationtest import ( "context" "fmt" - openapi_types "github.com/oapi-codegen/runtime/types" "net/http" + openapi_types "github.com/oapi-codegen/runtime/types" + "github.com/jiaozifs/jiaozifs/api" apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" "github.com/smartystreets/goconvey/convey" From 9cbd7ec492143bb15d96d563a484822dd5ebb54f Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Tue, 16 Jan 2024 13:42:17 +0800 Subject: [PATCH 149/210] fix: protect default branch --- controller/branch_ctl.go | 5 +++++ integrationtest/branch_test.go | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/controller/branch_ctl.go b/controller/branch_ctl.go index f21677c5..26e4a9e8 100644 --- a/controller/branch_ctl.go +++ b/controller/branch_ctl.go @@ -218,6 +218,11 @@ func (bct BranchController) DeleteBranch(ctx context.Context, w *api.JiaozifsRes return } + if params.RefName == repository.HEAD { + w.BadRequest("can not delete HEAD branch") + return + } + workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repository, bct.Repo, bct.PublicStorageConfig) if err != nil { w.Error(err) diff --git a/integrationtest/branch_test.go b/integrationtest/branch_test.go index 33d7965f..03e7e4fb 100644 --- a/integrationtest/branch_test.go +++ b/integrationtest/branch_test.go @@ -256,7 +256,11 @@ func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) }) - + c.Convey("fail to delete default branch", func() { + resp, err := client.DeleteBranch(ctx, userName, repoName, &api.DeleteBranchParams{RefName: "main"}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) createWip(ctx, c, client, "creat wip for delete", userName, repoName, "feat/sec_branch") c.Convey("delete branch successful", func() { resp, err := client.DeleteBranch(ctx, userName, repoName, &api.DeleteBranchParams{RefName: "feat/sec_branch"}) From b4e70b33c55bd792799c611e7b8c9e796a360748 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Tue, 16 Jan 2024 20:31:55 +0800 Subject: [PATCH 150/210] feat: add api for refresh token --- api/jiaozifs.gen.go | 288 +++++++++++++++++++++++++---------- api/swagger.yml | 28 ++++ controller/user_ctl.go | 19 ++- integrationtest/user_test.go | 8 + 4 files changed, 264 insertions(+), 79 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index f0684a4b..9c0529cf 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -753,6 +753,9 @@ type ClientInterface interface { // GetSetupState request GetSetupState(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // RefreshToken request + RefreshToken(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // RegisterWithBody request with any body RegisterWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -1147,6 +1150,18 @@ func (c *Client) GetSetupState(ctx context.Context, reqEditors ...RequestEditorF return c.Client.Do(req) } +func (c *Client) RefreshToken(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRefreshTokenRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) RegisterWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewRegisterRequestWithBody(c.Server, contentType, body) if err != nil { @@ -2821,6 +2836,33 @@ func NewGetSetupStateRequest(server string) (*http.Request, error) { return req, nil } +// NewRefreshTokenRequest generates requests for RefreshToken +func NewRefreshTokenRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/users/refreshtoken") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewRegisterRequest calls the generic Register builder with application/json body func NewRegisterRequest(server string, body RegisterJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -3700,6 +3742,9 @@ type ClientWithResponsesInterface interface { // GetSetupStateWithResponse request GetSetupStateWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetSetupStateResponse, error) + // RefreshTokenWithResponse request + RefreshTokenWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*RefreshTokenResponse, error) + // RegisterWithBodyWithResponse request with any body RegisterWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterResponse, error) @@ -4244,6 +4289,28 @@ func (r GetSetupStateResponse) StatusCode() int { return 0 } +type RefreshTokenResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *AuthenticationToken +} + +// Status returns HTTPResponse.Status +func (r RefreshTokenResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r RefreshTokenResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type RegisterResponse struct { Body []byte HTTPResponse *http.Response @@ -4781,6 +4848,15 @@ func (c *ClientWithResponses) GetSetupStateWithResponse(ctx context.Context, req return ParseGetSetupStateResponse(rsp) } +// RefreshTokenWithResponse request returning *RefreshTokenResponse +func (c *ClientWithResponses) RefreshTokenWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*RefreshTokenResponse, error) { + rsp, err := c.RefreshToken(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseRefreshTokenResponse(rsp) +} + // RegisterWithBodyWithResponse request with arbitrary body returning *RegisterResponse func (c *ClientWithResponses) RegisterWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterResponse, error) { rsp, err := c.RegisterWithBody(ctx, contentType, body, reqEditors...) @@ -5440,6 +5516,32 @@ func ParseGetSetupStateResponse(rsp *http.Response) (*GetSetupStateResponse, err return response, nil } +// ParseRefreshTokenResponse parses an HTTP response from a RefreshTokenWithResponse call +func ParseRefreshTokenResponse(rsp *http.Response) (*RefreshTokenResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &RefreshTokenResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest AuthenticationToken + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseRegisterResponse parses an HTTP response from a RegisterWithResponse call func ParseRegisterResponse(rsp *http.Response) (*RegisterResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -5809,6 +5911,9 @@ type ServerInterface interface { // check if jiaozifs setup // (GET /setup) GetSetupState(ctx context.Context, w *JiaozifsResponse, r *http.Request) + // refresh token for more time + // (GET /users/refreshtoken) + RefreshToken(ctx context.Context, w *JiaozifsResponse, r *http.Request) // perform user registration // (POST /users/register) Register(ctx context.Context, w *JiaozifsResponse, r *http.Request, body RegisterJSONRequestBody) @@ -5991,6 +6096,12 @@ func (_ Unimplemented) GetSetupState(ctx context.Context, w *JiaozifsResponse, r w.WriteHeader(http.StatusNotImplemented) } +// refresh token for more time +// (GET /users/refreshtoken) +func (_ Unimplemented) RefreshToken(ctx context.Context, w *JiaozifsResponse, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + // perform user registration // (POST /users/register) func (_ Unimplemented) Register(ctx context.Context, w *JiaozifsResponse, r *http.Request, body RegisterJSONRequestBody) { @@ -7431,6 +7542,25 @@ func (siw *ServerInterfaceWrapper) GetSetupState(w http.ResponseWriter, r *http. handler.ServeHTTP(w, r.WithContext(ctx)) } +// RefreshToken operation middleware +func (siw *ServerInterfaceWrapper) RefreshToken(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.RefreshToken(r.Context(), &JiaozifsResponse{w}, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // Register operation middleware func (siw *ServerInterfaceWrapper) Register(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -8255,6 +8385,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/setup", wrapper.GetSetupState) }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/users/refreshtoken", wrapper.RefreshToken) + }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/users/register", wrapper.Register) }) @@ -8302,85 +8435,86 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 6fd5f00d..3295d211 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -2117,3 +2117,31 @@ paths: description: Forbiden default: description: Internal Server Error + + /users/refreshtoken: + get: + tags: + - auth + operationId: refreshToken + summary: refresh token for more time + security: + - jwt_token: [ ] + - cookie_auth: [ ] + responses: + 200: + description: successful refresh token + headers: + Set-Cookie: + schema: + type: string + example: "internal_auth_session=abcde12356; Path=/; HttpOnly" + content: + application/json: + schema: + $ref: "#/components/schemas/AuthenticationToken" + 401: + description: Unauthorized ValidationError + 420: + description: too many requests + default: + description: Internal Server Error \ No newline at end of file diff --git a/controller/user_ctl.go b/controller/user_ctl.go index 1a4f842c..5c5457b8 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -48,6 +48,21 @@ func (userCtl UserController) Login(ctx context.Context, w *api.JiaozifsResponse w.Code(http.StatusUnauthorized) return } + + userCtl.generateAndRespToken(w, r, body.Name) +} + +func (userCtl UserController) RefreshToken(ctx context.Context, w *api.JiaozifsResponse, r *http.Request) { + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return + } + + userCtl.generateAndRespToken(w, r, operator.Name) +} + +func (userCtl UserController) generateAndRespToken(w *api.JiaozifsResponse, r *http.Request, name string) { // Generate user token loginTime := time.Now() expires := loginTime.Add(auth.ExpirationDuration) @@ -57,13 +72,13 @@ func (userCtl UserController) Login(ctx context.Context, w *api.JiaozifsResponse return } - tokenString, err := auth.GenerateJWTLogin(secretKey, body.Name, loginTime, expires) + tokenString, err := auth.GenerateJWTLogin(secretKey, name, loginTime, expires) if err != nil { w.Error(err) return } - userCtlLog.Infof("user %s login successful", body.Name) + userCtlLog.Infof("user %s login successful", name) internalAuthSession, _ := userCtl.SessionStore.Get(r, auth.InternalAuthSessionName) internalAuthSession.Values[auth.TokenSessionKeyName] = tokenString diff --git a/integrationtest/user_test.go b/integrationtest/user_test.go index c2392310..5294cd8f 100644 --- a/integrationtest/user_test.go +++ b/integrationtest/user_test.go @@ -45,5 +45,13 @@ func UserSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) }) + c.Convey("refresh token", func() { + resp, err := client.RefreshToken(ctx) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + _, err = api.ParseRefreshTokenResponse(resp) + convey.So(err, convey.ShouldBeNil) + }) } } From 24362d80d88b3b156f78d998c6845ba5012daf44 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Tue, 16 Jan 2024 22:13:53 +0800 Subject: [PATCH 151/210] feat: add simple token refresh --- api/swagger.yml | 2 +- auth/auth_middleware.go | 29 ++++++++++++++++++--------- auth/jwt_login.go | 27 ------------------------- auth/token.go | 39 +++++++++++++++++++++++++++++++++--- go.mod | 3 +-- go.sum | 6 ++---- integrationtest/user_test.go | 9 +++++++++ 7 files changed, 69 insertions(+), 46 deletions(-) delete mode 100644 auth/jwt_login.go diff --git a/api/swagger.yml b/api/swagger.yml index 3295d211..86194c76 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -2144,4 +2144,4 @@ paths: 420: description: too many requests default: - description: Internal Server Error \ No newline at end of file + description: Internal Server Error diff --git a/auth/auth_middleware.go b/auth/auth_middleware.go index b4459180..3611c378 100644 --- a/auth/auth_middleware.go +++ b/auth/auth_middleware.go @@ -3,9 +3,12 @@ package auth import ( "context" "errors" + "fmt" "net/http" "strings" + "github.com/golang-jwt/jwt/v5" + "github.com/getkin/kin-openapi/openapi3" "github.com/getkin/kin-openapi/routers" "github.com/getkin/kin-openapi/routers/legacy" @@ -110,7 +113,7 @@ func checkSecurityRequirements(r *http.Request, continue } token := parts[1] - user, err = userByToken(ctx, secretStore, userRepo, token) + user, err = userByToken(ctx, userRepo, secretStore.SharedSecret(), token) case "basic_auth": // validate using basic auth accessKey, secretKey, ok := r.BasicAuth() @@ -128,7 +131,7 @@ func checkSecurityRequirements(r *http.Request, if token == "" { continue } - user, err = userByToken(ctx, secretStore, userRepo, token) + user, err = userByToken(ctx, userRepo, secretStore.SharedSecret(), token) default: // unknown security requirement to check log.With("provider", provider).Error("Authentication middleware unknown security requirement provider") @@ -146,20 +149,28 @@ func checkSecurityRequirements(r *http.Request, return nil, nil } -func userByToken(ctx context.Context, secretStore crypt.SecretStore, userRepo models.IUserRepo, tokenString string) (*models.User, error) { - claims, err := VerifyToken(secretStore.SharedSecret(), tokenString) - // make sure no audience is set for login token - if err != nil || !claims.VerifyAudience(LoginAudience, false) { +func userByToken(ctx context.Context, userRepo models.IUserRepo, secret []byte, tokenString string) (*models.User, error) { + claims, err := VerifyToken(secret, tokenString) + if err != nil { return nil, ErrAuthenticatingRequest } - username := claims.Subject + // make sure no audience is set for login token + validator := jwt.NewValidator(jwt.WithAudience(LoginAudience)) + if err = validator.Validate(claims); err != nil { + return nil, fmt.Errorf("invalid token: %s %w", err, ErrAuthenticatingRequest) + } + + username, err := claims.GetSubject() + if err != nil { + return nil, err + } userData, err := userRepo.Get(ctx, models.NewGetUserParams().SetName(username)) if err != nil { log.With( - "token_id", claims.Id, + "token", tokenString, "username", username, - "subject", claims.Subject, + "subject", username, ).Debugf("could not find user id by credentials %v", err) return nil, ErrAuthenticatingRequest } diff --git a/auth/jwt_login.go b/auth/jwt_login.go deleted file mode 100644 index 0dbd533f..00000000 --- a/auth/jwt_login.go +++ /dev/null @@ -1,27 +0,0 @@ -package auth - -import ( - "time" - - "github.com/golang-jwt/jwt" - "github.com/google/uuid" -) - -const ( - LoginAudience = "login" -) - -// GenerateJWTLogin creates a jwt token which can be used for authentication during login only, i.e. it will not work for password reset. -// It supports backward compatibility for creating a login jwt. The audience is not set for login token. Any audience will make the token -// invalid for login. No email is passed to support the ability of login for users via user/access keys which don't have an email yet -func GenerateJWTLogin(secret []byte, userID string, issuedAt, expiresAt time.Time) (string, error) { - claims := jwt.MapClaims{ - "id": uuid.NewString(), - "aud": LoginAudience, - "sub": userID, - "iat": issuedAt.Unix(), - "exp": expiresAt.Unix(), - } - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - return token.SignedString(secret) -} diff --git a/auth/token.go b/auth/token.go index 05a59f4d..e6b13968 100644 --- a/auth/token.go +++ b/auth/token.go @@ -3,16 +3,48 @@ package auth import ( "errors" "fmt" + "time" - "github.com/golang-jwt/jwt" + "github.com/google/uuid" + + "github.com/golang-jwt/jwt/v5" ) var ( ErrUnexpectedSigningMethod = errors.New("unexpected signing method") ) -func VerifyToken(secret []byte, tokenString string) (*jwt.StandardClaims, error) { - claims := &jwt.StandardClaims{} +const ( + LoginAudience = "login" +) + +// GenerateJWTLogin creates a jwt token which can be used for authentication during login only, i.e. it will not work for password reset. +// It supports backward compatibility for creating a login jwt. The audience is not set for login token. Any audience will make the token +// invalid for login. No email is passed to support the ability of login for users via user/access keys which don't have an email yet +func GenerateJWTLogin(secret []byte, userID string, issuedAt, expiresAt time.Time) (string, error) { + claims := jwt.MapClaims{ + "id": uuid.NewString(), + "aud": LoginAudience, + "sub": userID, + "iat": issuedAt.Unix(), + "exp": expiresAt.Unix(), + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString(secret) +} + +// VerifyToken verifies the authenticity of a token using a secret key. +// +// It takes in the following parameters: +// - secret []byte: the secret key used to sign the token +// - tokenString string: the token string to be verified +// +// It returns the following: +// - jwt.Claims: the claims extracted from the token +// - error: any error encountered during token verification +func VerifyToken(secret []byte, tokenString string) (jwt.Claims, error) { + claims := &jwt.MapClaims{} token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("%w: %s", ErrUnexpectedSigningMethod, token.Header["alg"]) @@ -22,5 +54,6 @@ func VerifyToken(secret []byte, tokenString string) (*jwt.StandardClaims, error) if err != nil || !token.Valid { return nil, ErrInvalidToken } + return claims, nil } diff --git a/go.mod b/go.mod index 95b6090b..9120441e 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/go-chi/chi/v5 v5.0.10 github.com/go-openapi/swag v0.22.4 github.com/go-test/deep v1.1.0 - github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/golang-jwt/jwt/v5 v5.2.0 github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.4.0 github.com/gorilla/sessions v1.2.2 @@ -103,7 +103,6 @@ require ( github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v5 v5.0.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/s2a-go v0.1.7 // indirect diff --git a/go.sum b/go.sum index 0b539cee..532175b6 100644 --- a/go.sum +++ b/go.sum @@ -191,10 +191,8 @@ github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= -github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= +github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= diff --git a/integrationtest/user_test.go b/integrationtest/user_test.go index 5294cd8f..9668ff4d 100644 --- a/integrationtest/user_test.go +++ b/integrationtest/user_test.go @@ -53,5 +53,14 @@ func UserSpec(ctx context.Context, urlStr string) func(c convey.C) { _, err = api.ParseRefreshTokenResponse(resp) convey.So(err, convey.ShouldBeNil) }) + + c.Convey("no auth refresh", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.RefreshToken(ctx) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + client.RequestEditors = re + }) } } From 7b30191793b0662259694f2bbd0e00e83e048646 Mon Sep 17 00:00:00 2001 From: zjy Date: Fri, 19 Jan 2024 19:11:47 +0800 Subject: [PATCH 152/210] feat: not contain null&NTFS mv check to /validator add validator test, --- controller/branch_ctl.go | 40 +-------- controller/object_ctl.go | 12 +-- controller/repository_ctl.go | 22 +---- controller/user_ctl.go | 13 +-- controller/validator/validate.go | 83 ++++++++++++++++++ controller/validator/validate_test.go | 121 ++++++++++++++++++++++++++ go.sum | 32 ------- integrationtest/branch_test.go | 4 +- 8 files changed, 215 insertions(+), 112 deletions(-) create mode 100644 controller/validator/validate.go create mode 100644 controller/validator/validate_test.go diff --git a/controller/branch_ctl.go b/controller/branch_ctl.go index 0515225e..51fd4d30 100644 --- a/controller/branch_ctl.go +++ b/controller/branch_ctl.go @@ -3,14 +3,10 @@ package controller import ( "context" "errors" - "fmt" - "net/http" - "regexp" - "strings" - "github.com/jiaozifs/jiaozifs/block/params" - + "github.com/jiaozifs/jiaozifs/controller/validator" "github.com/jiaozifs/jiaozifs/versionmgr" + "net/http" "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/auth" @@ -19,36 +15,6 @@ import ( "go.uber.org/fx" ) -var MaxBranchNameLength = 40 -var branchNameRegex = regexp.MustCompile(`^[\w]+\/?[\w]+$`) - -func CheckBranchName(name string) error { - for _, blackName := range RepoNameBlackList { - if name == blackName { - return errors.New("repository name is black list") - } - } - - if len(name) > MaxBranchNameLength { - return fmt.Errorf("branch name is too long") - } - - seg := strings.Split(name, "/") - if len(seg) > 2 { - return fmt.Errorf("branch format must be or /") - } - - if !branchNameRegex.Match([]byte(seg[0])) { - return fmt.Errorf("invalid branch name: %s, must start with a number or letter and can only contain numbers, letters, hyphens or underscores", seg[0]) - } - if len(seg) > 2 { - if !branchNameRegex.Match([]byte(seg[1])) { - return fmt.Errorf("invalid branch name: %s, must start with a number or letter and can only contain numbers, letters, hyphens or underscores", seg[1]) - } - } - return nil -} - type BranchController struct { fx.In @@ -127,7 +93,7 @@ func (bct BranchController) ListBranches(ctx context.Context, w *api.JiaozifsRes } func (bct BranchController) CreateBranch(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.CreateBranchJSONRequestBody, ownerName string, repositoryName string) { - if err := CheckBranchName(body.Name); err != nil { + if err := validator.ValidateBranchName(body.Name); err != nil { w.BadRequest(err.Error()) return } diff --git a/controller/object_ctl.go b/controller/object_ctl.go index dd027010..6c44546b 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -4,11 +4,11 @@ import ( "context" "errors" "fmt" + "github.com/jiaozifs/jiaozifs/controller/validator" "io" "mime" "mime/multipart" "net/http" - "regexp" "time" "github.com/go-openapi/swag" @@ -26,14 +26,6 @@ import ( ) var objLog = logging.Logger("object_ctl") -var pathRegex = regexp.MustCompile(`^([a-zA-Z0-9]+\/)*[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)?$`) //nolint - -func CheckObjectPath(path string) error { - if !pathRegex.MatchString(path) { - return fmt.Errorf("invalid object path: it must start with a letter or digit, can contain letters, digits, and must be separated by slashes") - } - return nil -} type ObjectController struct { fx.In @@ -321,7 +313,7 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes } defer reader.Close() //nolint - err = CheckObjectPath(params.Path) + err = validator.ValidateObjectPath(params.Path) if err != nil { w.BadRequest(err.Error()) return diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index 91de9806..75a16e1f 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -3,11 +3,10 @@ package controller import ( "context" "encoding/json" - "errors" "fmt" + "github.com/jiaozifs/jiaozifs/controller/validator" "io" "net/http" - "regexp" "time" "github.com/google/uuid" @@ -27,23 +26,6 @@ import ( const DefaultBranchName = "main" var repoLog = logging.Logger("repo control") -var alphanumeric = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_\-]{1,61}[a-zA-Z0-9]$`) - -// RepoNameBlackList forbid repo name, reserve for routes -var RepoNameBlackList = []string{"repository", "repositories", "wip", "wips", "object", "objects", "commit", "commits", "ref", "refs", "repo", "repos", "user", "users"} - -func CheckRepositoryName(name string) error { - for _, blackName := range RepoNameBlackList { - if name == blackName { - return errors.New("repository name is black list") - } - } - - if !alphanumeric.MatchString(name) { - return errors.New("repository name must start with a number or letter, can only contain numbers, letters, or hyphens, and must be between 3 and 63 characters in length") - } - return nil -} type RepositoryController struct { fx.In @@ -150,7 +132,7 @@ func (repositoryCtl RepositoryController) ListRepository(ctx context.Context, w } func (repositoryCtl RepositoryController) CreateRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.CreateRepositoryJSONRequestBody) { - err := CheckRepositoryName(body.Name) + err := validator.ValidateRepoName(body.Name) if err != nil { w.BadRequest(err.Error()) return diff --git a/controller/user_ctl.go b/controller/user_ctl.go index 8571b1d7..b3d18d62 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -3,9 +3,8 @@ package controller import ( "context" "encoding/hex" - "errors" + "github.com/jiaozifs/jiaozifs/controller/validator" "net/http" - "regexp" "time" "github.com/jiaozifs/jiaozifs/utils" @@ -23,19 +22,11 @@ import ( ) var userCtlLog = logging.Logger("user_ctl") -var usernameRegex = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_-]{1,28}[a-zA-Z0-9]$`) const ( AuthHeader = "Authorization" ) -func CheckUserName(name string) error { - if !usernameRegex.MatchString(name) { - return errors.New("invalid username: it must start and end with a letter or digit, can contain letters, digits, hyphens, and cannot start or end with a hyphen; the length must be between 3 and 30 characters") - } - return nil -} - type UserController struct { fx.In @@ -90,7 +81,7 @@ func (userCtl UserController) Login(ctx context.Context, w *api.JiaozifsResponse } func (userCtl UserController) Register(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.RegisterJSONRequestBody) { - err := CheckUserName(body.Name) + err := validator.ValidateUsername(body.Name) if err != nil { w.BadRequest(err.Error()) return diff --git a/controller/validator/validate.go b/controller/validator/validate.go new file mode 100644 index 00000000..7e855b34 --- /dev/null +++ b/controller/validator/validate.go @@ -0,0 +1,83 @@ +package validator + +import ( + "errors" + "regexp" + "strings" +) + +var ( + MaxBranchNameLength = 40 + + ReValidRef = regexp.MustCompile(`^\w+/?\w+$`) + ReValidRepo = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_\-]{1,61}[a-zA-Z0-9]$`) + ReValidUser = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_-]{1,28}[a-zA-Z0-9]$`) + ReValidPath = regexp.MustCompile(`^(?:[^\x00-\x1F\\/:*?"<>|]+/)*[^\x00-\x1F\\/:*?"<>|]+$`) + + // RepoNameBlackList forbid repo name, reserve for routes + RepoNameBlackList = []string{"repository", "repositories", "wip", "wips", "object", "objects", "commit", "commits", "ref", "refs", "repo", "repos", "user", "users"} +) + +var ( + ErrNameBlackList = errors.New("repository name is black list") + ErrNameTooLong = errors.New("name too long") + ErrBranchFormat = errors.New("branch format must be or /") + ErrInvalidBranchName = errors.New("invalid branch name: must start with a number or letter and can only contain numbers, letters, hyphens or underscores") + ErrInvalidRepoName = errors.New("repository name must start with a number or letter, can only contain numbers, letters, or hyphens, and must be between 3 and 63 characters in length") + ErrInvalidUsername = errors.New("invalid username: it must start and end with a letter or digit, can contain letters, digits, hyphens, and cannot start or end with a hyphen; the length must be between 3 and 30 characters") + ErrInvalidObjectPath = errors.New("invalid object path: it must not contain null characters or NTFS forbidden characters") +) + +func ValidateBranchName(name string) error { + for _, blackName := range RepoNameBlackList { + if name == blackName { + return ErrNameBlackList + } + } + + if len(name) > MaxBranchNameLength { + return ErrNameTooLong + } + + seg := strings.Split(name, "/") + if len(seg) > 2 { + return ErrBranchFormat + } + + if !ReValidRef.Match([]byte(seg[0])) { + return ErrInvalidBranchName + } + if len(seg) > 1 { + if !ReValidRef.Match([]byte(seg[1])) { + return ErrInvalidBranchName + } + } + return nil +} + +func ValidateRepoName(name string) error { + for _, blackName := range RepoNameBlackList { + if name == blackName { + return ErrNameBlackList + } + } + + if !ReValidRepo.MatchString(name) { + return ErrInvalidRepoName + } + return nil +} + +func ValidateUsername(name string) error { + if !ReValidUser.MatchString(name) { + return ErrInvalidUsername + } + return nil +} + +func ValidateObjectPath(path string) error { + if !ReValidPath.MatchString(path) { + return ErrInvalidObjectPath + } + return nil +} diff --git a/controller/validator/validate_test.go b/controller/validator/validate_test.go new file mode 100644 index 00000000..c0f244f7 --- /dev/null +++ b/controller/validator/validate_test.go @@ -0,0 +1,121 @@ +package validator + +import ( + "testing" +) + +func TestValidateBranchName(t *testing.T) { + //Validate branch names + validBranchNames := []string{"main", "feat/branch", "fix/bugfix"} + for _, name := range validBranchNames { + err := ValidateBranchName(name) + if err != nil { + t.Errorf("Expected no error for branch name '%s', but got: %s", name, err) + } + } + + //Invalidate branch names + invalidBranchNames := []struct { + name string + error string + }{ + {"repository", "repository name is black list"}, + {"wip", "repository name is black list"}, + {"too_long_branch_name_that_exceeds_max_length_limit", "name too long"}, + {"invalid/name\x00", "invalid branch name: must start with a number or letter and can only contain numbers, letters, hyphens or underscores"}, + {"invalid/branch/name", "branch format must be or /"}, + } + + for _, testCase := range invalidBranchNames { + err := ValidateBranchName(testCase.name) + if err == nil || err.Error() != testCase.error { + t.Errorf("Expected error '%s' for invalid branch name '%s', but got: %v", testCase.error, testCase.name, err) + } + } +} + +func TestValidateRepoName(t *testing.T) { + //Validate Repo names + validRepoNames := []string{"myrepo", "user123", "project-name", "repo123_name"} + for _, name := range validRepoNames { + err := ValidateRepoName(name) + if err != nil { + t.Errorf("Expected no error for repo name '%s', but got: %s", name, err) + } + } + + //Invalidate Repo names + invalidRepoNames := []struct { + name string + error string + }{ + {"repository", "repository name is black list"}, + {"wip", "repository name is black list"}, + {"invalid/name", "repository name must start with a number or letter, can only contain numbers, letters, or hyphens, and must be between 3 and 63 characters in length"}, + } + + for _, testCase := range invalidRepoNames { + err := ValidateRepoName(testCase.name) + if err == nil || err.Error() != testCase.error { + t.Errorf("Expected error '%s' for invalid repo name '%s', but got: %v", testCase.error, testCase.name, err) + } + } +} + +func TestValidateUsername(t *testing.T) { + //Validate Username + validUsernames := []string{"user123", "username", "user_name", "user-123"} + for _, name := range validUsernames { + err := ValidateUsername(name) + if err != nil { + t.Errorf("Expected no error for username '%s', but got: %s", name, err) + } + } + + //Invalidate Username + invalidUsernames := []struct { + name string + error string + }{ + {"user name", "invalid username: it must start and end with a letter or digit, can contain letters, digits, hyphens, and cannot start or end with a hyphen; the length must be between 3 and 30 characters"}, + {"user-with-hyphen-", "invalid username: it must start and end with a letter or digit, can contain letters, digits, hyphens, and cannot start or end with a hyphen; the length must be between 3 and 30 characters"}, + {"invalid/username", "invalid username: it must start and end with a letter or digit, can contain letters, digits, hyphens, and cannot start or end with a hyphen; the length must be between 3 and 30 characters"}, + } + + for _, testCase := range invalidUsernames { + err := ValidateUsername(testCase.name) + if err == nil || err.Error() != testCase.error { + t.Errorf("Expected error '%s' for invalid username '%s', but got: %v", testCase.error, testCase.name, err) + } + } +} + +func TestValidateObjectPath(t *testing.T) { + //Validate Obj Path + validObjectPaths := []string{"path/to/object", "file.txt", "folder/file.txt"} + for _, path := range validObjectPaths { + err := ValidateObjectPath(path) + if err != nil { + t.Errorf("Expected no error for object path '%s', but got: %s", path, err) + } + } + + //Invalidate Obj Path + invalidObjectPaths := []struct { + path string + error string + }{ + {"path/with/null\x00character", "invalid object path: it must not contain null characters or NTFS forbidden characters"}, + {"path/with/invalid/characters/:", "invalid object path: it must not contain null characters or NTFS forbidden characters"}, + {"path/with/invalid/characters/*", "invalid object path: it must not contain null characters or NTFS forbidden characters"}, + {"path/with/invalid/characters/\"", "invalid object path: it must not contain null characters or NTFS forbidden characters"}, + {"path/with/invalid/characters/ Date: Fri, 19 Jan 2024 19:42:29 +0800 Subject: [PATCH 153/210] fix: fix goimports & remove obj path test --- controller/branch_ctl.go | 3 ++- controller/object_ctl.go | 3 ++- controller/repository_ctl.go | 3 ++- controller/user_ctl.go | 3 ++- integrationtest/branch_test.go | 3 ++- integrationtest/objects_test.go | 9 --------- 6 files changed, 10 insertions(+), 14 deletions(-) diff --git a/controller/branch_ctl.go b/controller/branch_ctl.go index 51fd4d30..1244ae8f 100644 --- a/controller/branch_ctl.go +++ b/controller/branch_ctl.go @@ -3,10 +3,11 @@ package controller import ( "context" "errors" + "net/http" + "github.com/jiaozifs/jiaozifs/block/params" "github.com/jiaozifs/jiaozifs/controller/validator" "github.com/jiaozifs/jiaozifs/versionmgr" - "net/http" "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/auth" diff --git a/controller/object_ctl.go b/controller/object_ctl.go index 6c44546b..db0af09a 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -4,13 +4,14 @@ import ( "context" "errors" "fmt" - "github.com/jiaozifs/jiaozifs/controller/validator" "io" "mime" "mime/multipart" "net/http" "time" + "github.com/jiaozifs/jiaozifs/controller/validator" + "github.com/go-openapi/swag" logging "github.com/ipfs/go-log/v2" "github.com/jiaozifs/jiaozifs/api" diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index 75a16e1f..b82875c9 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -4,11 +4,12 @@ import ( "context" "encoding/json" "fmt" - "github.com/jiaozifs/jiaozifs/controller/validator" "io" "net/http" "time" + "github.com/jiaozifs/jiaozifs/controller/validator" + "github.com/google/uuid" logging "github.com/ipfs/go-log/v2" "github.com/jiaozifs/jiaozifs/api" diff --git a/controller/user_ctl.go b/controller/user_ctl.go index b3d18d62..53ff36a5 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -3,10 +3,11 @@ package controller import ( "context" "encoding/hex" - "github.com/jiaozifs/jiaozifs/controller/validator" "net/http" "time" + "github.com/jiaozifs/jiaozifs/controller/validator" + "github.com/jiaozifs/jiaozifs/utils" "github.com/go-openapi/swag" diff --git a/integrationtest/branch_test.go b/integrationtest/branch_test.go index c6e0ae51..655adb06 100644 --- a/integrationtest/branch_test.go +++ b/integrationtest/branch_test.go @@ -2,10 +2,11 @@ package integrationtest import ( "context" - "github.com/jiaozifs/jiaozifs/controller/validator" "net/http" "strings" + "github.com/jiaozifs/jiaozifs/controller/validator" + "github.com/jiaozifs/jiaozifs/api" apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" "github.com/jiaozifs/jiaozifs/utils" diff --git a/integrationtest/objects_test.go b/integrationtest/objects_test.go index 587d9ad6..0472348f 100644 --- a/integrationtest/objects_test.go +++ b/integrationtest/objects_test.go @@ -93,15 +93,6 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) }) - c.Convey("invalid path", func() { - resp, err := client.UploadObjectWithBody(ctx, userName, repoName, &api.UploadObjectParams{ - RefName: branchName, - Path: "a~a.bin", - }, "application/octet-stream", bytes.NewReader([]byte{1, 2, 3, 4, 5, 6, 7, 8})) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) - }) - c.Convey("no sufix file", func() { resp, err := client.UploadObjectWithBody(ctx, userName, repoName, &api.UploadObjectParams{ RefName: branchName, From d87db482082c3cc201e4f7d592b533cc63ac14a6 Mon Sep 17 00:00:00 2001 From: zjy Date: Tue, 23 Jan 2024 11:17:29 +0800 Subject: [PATCH 154/210] test: add Chinese test for repo path --- controller/validator/validate_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/validator/validate_test.go b/controller/validator/validate_test.go index c0f244f7..06fb7310 100644 --- a/controller/validator/validate_test.go +++ b/controller/validator/validate_test.go @@ -92,7 +92,7 @@ func TestValidateUsername(t *testing.T) { func TestValidateObjectPath(t *testing.T) { //Validate Obj Path - validObjectPaths := []string{"path/to/object", "file.txt", "folder/file.txt"} + validObjectPaths := []string{"path/to/object", "file.txt", "folder/file.txt", "我的图片.png", "我的文件/我的应用.exe"} for _, path := range validObjectPaths { err := ValidateObjectPath(path) if err != nil { From fa227da44275abf9b80eec7611aaed20cc0cac2a Mon Sep 17 00:00:00 2001 From: zjy Date: Tue, 23 Jan 2024 11:24:42 +0800 Subject: [PATCH 155/210] test: add other languages test for repo path --- controller/validator/validate_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/validator/validate_test.go b/controller/validator/validate_test.go index 06fb7310..c7ae4efb 100644 --- a/controller/validator/validate_test.go +++ b/controller/validator/validate_test.go @@ -92,7 +92,7 @@ func TestValidateUsername(t *testing.T) { func TestValidateObjectPath(t *testing.T) { //Validate Obj Path - validObjectPaths := []string{"path/to/object", "file.txt", "folder/file.txt", "我的图片.png", "我的文件/我的应用.exe"} + validObjectPaths := []string{"path/to/object", "file.txt", "folder/file.txt", "我的图片.png", "我的文件/我的应用.exe", "私のビデオ.mp3"} for _, path := range validObjectPaths { err := ValidateObjectPath(path) if err != nil { From 22ab32c9e029e6b413adedd5687a53b76453fdf2 Mon Sep 17 00:00:00 2001 From: zjy Date: Tue, 23 Jan 2024 11:30:54 +0800 Subject: [PATCH 156/210] test: add other languages test for all test --- controller/validator/validate_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/controller/validator/validate_test.go b/controller/validator/validate_test.go index c7ae4efb..fe7b182e 100644 --- a/controller/validator/validate_test.go +++ b/controller/validator/validate_test.go @@ -23,6 +23,8 @@ func TestValidateBranchName(t *testing.T) { {"wip", "repository name is black list"}, {"too_long_branch_name_that_exceeds_max_length_limit", "name too long"}, {"invalid/name\x00", "invalid branch name: must start with a number or letter and can only contain numbers, letters, hyphens or underscores"}, + {"invalid/名称", "invalid branch name: must start with a number or letter and can only contain numbers, letters, hyphens or underscores"}, + {"invalid/지점명", "invalid branch name: must start with a number or letter and can only contain numbers, letters, hyphens or underscores"}, {"invalid/branch/name", "branch format must be or /"}, } @@ -52,6 +54,8 @@ func TestValidateRepoName(t *testing.T) { {"repository", "repository name is black list"}, {"wip", "repository name is black list"}, {"invalid/name", "repository name must start with a number or letter, can only contain numbers, letters, or hyphens, and must be between 3 and 63 characters in length"}, + {"分支/name", "repository name must start with a number or letter, can only contain numbers, letters, or hyphens, and must be between 3 and 63 characters in length"}, + {"私は支店です/name", "repository name must start with a number or letter, can only contain numbers, letters, or hyphens, and must be between 3 and 63 characters in length"}, } for _, testCase := range invalidRepoNames { @@ -78,6 +82,8 @@ func TestValidateUsername(t *testing.T) { error string }{ {"user name", "invalid username: it must start and end with a letter or digit, can contain letters, digits, hyphens, and cannot start or end with a hyphen; the length must be between 3 and 30 characters"}, + {"张三三", "invalid username: it must start and end with a letter or digit, can contain letters, digits, hyphens, and cannot start or end with a hyphen; the length must be between 3 and 30 characters"}, + {"モンキー・D・ルフィ", "invalid username: it must start and end with a letter or digit, can contain letters, digits, hyphens, and cannot start or end with a hyphen; the length must be between 3 and 30 characters"}, {"user-with-hyphen-", "invalid username: it must start and end with a letter or digit, can contain letters, digits, hyphens, and cannot start or end with a hyphen; the length must be between 3 and 30 characters"}, {"invalid/username", "invalid username: it must start and end with a letter or digit, can contain letters, digits, hyphens, and cannot start or end with a hyphen; the length must be between 3 and 30 characters"}, } From f3b3229523578f7184f5f4accc70dbe0fa6ee0f6 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Wed, 17 Jan 2024 20:06:38 +0800 Subject: [PATCH 157/210] feat: support make docker images --- Dockerfile | 11 +++++++++++ README.md | 4 ++++ charts/Chart.yaml | 13 ++++++++++++ charts/templates/deployment.yaml | 24 ++++++++++++++++++++++ charts/templates/ingress.yaml | 28 ++++++++++++++++++++++++++ charts/templates/service.yaml | 14 +++++++++++++ charts/values.yaml | 10 ++++++++++ cmd/daemon.go | 14 +++++++------ cmd/root.go | 5 +++-- makefile | 5 +++++ models/migrations/main.go | 6 ------ script/postgress/configmap.yaml | 11 +++++++++++ script/postgress/deployment.yaml | 34 ++++++++++++++++++++++++++++++++ script/postgress/pv.yaml | 15 ++++++++++++++ script/postgress/pvc.yaml | 13 ++++++++++++ script/postgress/svc.yaml | 14 +++++++++++++ script/start.sh | 17 ++++++++++++++++ 17 files changed, 224 insertions(+), 14 deletions(-) create mode 100644 Dockerfile create mode 100644 charts/Chart.yaml create mode 100644 charts/templates/deployment.yaml create mode 100644 charts/templates/ingress.yaml create mode 100644 charts/templates/service.yaml create mode 100644 charts/values.yaml create mode 100644 script/postgress/configmap.yaml create mode 100644 script/postgress/deployment.yaml create mode 100644 script/postgress/pv.yaml create mode 100644 script/postgress/pvc.yaml create mode 100644 script/postgress/svc.yaml create mode 100755 script/start.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..6be840a0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM ubuntu:22.04 + + +WORKDIR /app + +COPY jzfs /jzfs +COPY script/start.sh /start.sh + +RUN chmod +x /start.sh + +ENTRYPOINT ["/start.sh"] diff --git a/README.md b/README.md index 818a85e7..91250a1c 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,10 @@ After following the above steps, you should be able to see an executable file na ./jzfs daemon ``` +#### run with docker +```bash +docker run -v :/app -p 34913:34913 gitdatateam/jzfs:latest --db "postgres://:@192.168.1.16:5432/jiaozifs?sslmode=disable" --bs_path /app/data --listen http://0.0.0.0:34913 --config /app/config.toml +``` ## License Dual-licensed under [MIT](https://github.com/jiaozifs/jiaozifs/blob/main/LICENSE-MIT) + [Apache 2.0](https://github.com/jiaozifs/jiaozifs/blob/main/LICENSE-APACHE) diff --git a/charts/Chart.yaml b/charts/Chart.yaml new file mode 100644 index 00000000..30f7a4e7 --- /dev/null +++ b/charts/Chart.yaml @@ -0,0 +1,13 @@ +apiVersion: v2 +name: jiaozifs-api +description: Install jiaozifs api to provider backend function of jiaozifs. +version: 1.0.0 +kubeVersion: < 1.28.0-0 +home: https://jiaozifs.com/ +keywords: + - jiaozifs +sources: + - https://github.com/jiaozifs/jiaozifs + - https://github.com/jiaozifs/jiaozifs-ui +maintainers: + - name: jiaozifs team diff --git a/charts/templates/deployment.yaml b/charts/templates/deployment.yaml new file mode 100644 index 00000000..3018839b --- /dev/null +++ b/charts/templates/deployment.yaml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: jiaozifs-backend-api-deployment + labels: + apptype: jiaozifs-api +spec: + replicas: 1 + selector: + matchLabels: + app: jiaozifs-backend-api + template: + metadata: + labels: + app: jiaozifs-backend-api + apptype: jiaozifs-api + spec: + containers: + - name: jiaozifs-backend + image: gitdatateam/jzfs:latest + imagePullPolicy: Always + args: ["--db {{ .Values.db }} --log-level {{ .Values.log_level }} --bs_path {{ .Values.bs_path }} --listen http://0.0.0.0:{{ .Values.port }} --config {{ .Values.config }}"] + ports: + - containerPort: {{ .Values.port }} diff --git a/charts/templates/ingress.yaml b/charts/templates/ingress.yaml new file mode 100644 index 00000000..b8b4c918 --- /dev/null +++ b/charts/templates/ingress.yaml @@ -0,0 +1,28 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: jiaozifs-api + annotations: + meta.helm.sh/release-name: jiaozifs-api + nginx.ingress.kubernetes.io/rewrite-target: / + nginx.ingress.kubernetes.io/proxy-connect-timeout: "30" + nginx.ingress.kubernetes.io/proxy-read-timeout: "1800" + nginx.ingress.kubernetes.io/proxy-send-timeout: "1800" + labels: + apptype: jiaozifs-api + app.kubernetes.io/managed-by: Helm + heritage: Helm + release: jiaozifs-api +spec: + ingressClassName: {{.Values.ingress_name}} + rules: + - host: api.jiaozifs.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: jiaozifs-backend-api-service + port: + number: {{ .Values.port }} diff --git a/charts/templates/service.yaml b/charts/templates/service.yaml new file mode 100644 index 00000000..7abcd843 --- /dev/null +++ b/charts/templates/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: jiaozifs-backend-api-service + labels: + apptype: jiaozifs-api +spec: + type: ClusterIP + selector: + app: jiaozifs-backend-api + ports: + - protocol: TCP + port: {{ .Values.port }} + targetPort: {{ .Values.port }} diff --git a/charts/values.yaml b/charts/values.yaml new file mode 100644 index 00000000..59b3eb91 --- /dev/null +++ b/charts/values.yaml @@ -0,0 +1,10 @@ +# Additional Trusted CAs. +# Enable this flag and add your CA certs as a secret named tls-ca-additional in the namespace. +# See README.md for details. +replicas: 1 +db: "" +bs_path: "/app/data" +config: "/app/config.toml" +port: 34913 +ingress_name: nginx +log_level: info diff --git a/cmd/daemon.go b/cmd/daemon.go index 93e5e621..5523cb02 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -3,6 +3,8 @@ package cmd import ( "context" + "github.com/pelletier/go-toml/v2" + "github.com/gorilla/sessions" logging "github.com/ipfs/go-log/v2" apiImpl "github.com/jiaozifs/jiaozifs/api/api_impl" @@ -16,7 +18,6 @@ import ( "github.com/jiaozifs/jiaozifs/utils" "github.com/jiaozifs/jiaozifs/version" "github.com/spf13/cobra" - "github.com/spf13/viper" "github.com/uptrace/bun" ) @@ -38,6 +39,12 @@ var daemonCmd = &cobra.Command{ return err } + cfgData, err := toml.Marshal(cfg) + if err != nil { + return err + } + log.Debug(string(cfgData)) + shutdown := make(utils.Shutdown) stop, err := fx_opt.New(cmd.Context(), fx_opt.Override(new(context.Context), cmd.Context()), @@ -76,9 +83,4 @@ var daemonCmd = &cobra.Command{ func init() { rootCmd.AddCommand(daemonCmd) - daemonCmd.Flags().String("db", "", "pg connection string eg. postgres://user:pass@localhost:5432/jiaozifs?sslmode=disable") - daemonCmd.Flags().String("log-level", "INFO", "set log level eg. DEBUG INFO ERROR") - - _ = viper.BindPFlag("database.connection", daemonCmd.Flags().Lookup("db")) - _ = viper.BindPFlag("log.level", daemonCmd.Flags().Lookup("log-level")) } diff --git a/cmd/root.go b/cmd/root.go index fb796249..19199866 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,7 +3,6 @@ package cmd import ( "os" - "github.com/jiaozifs/jiaozifs/config" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -31,7 +30,9 @@ func RootCmd() *cobra.Command { } func init() { rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "~/.jiaozifs/config.toml", "config file (default is $HOME/.jiaozifs/config.toml)") - rootCmd.PersistentFlags().String("listen", config.DefaultLocalBSPath, "config blockstore path") + rootCmd.PersistentFlags().String("listen", "http://127.0.0.1:34913", "config list url") + rootCmd.PersistentFlags().String("log-level", "INFO", "set log level eg. DEBUG INFO ERROR") _ = viper.BindPFlag("api.listen", rootCmd.PersistentFlags().Lookup("listen")) _ = viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config")) + _ = viper.BindPFlag("log.level", rootCmd.PersistentFlags().Lookup("log-level")) } diff --git a/makefile b/makefile index 346c6f6d..a43e12a3 100644 --- a/makefile +++ b/makefile @@ -27,3 +27,8 @@ test: gen-api go test -timeout=30m -parallel=4 -v ./... build:gen-api go build $(GOFLAGS) -o jzfs + +TAG:=test +docker:build + docker build -t gitdatateam/jzfs:$(TAG) . + docker push gitdatateam/jzfs:$(TAG) diff --git a/models/migrations/main.go b/models/migrations/main.go index dce0dfde..15012e73 100644 --- a/models/migrations/main.go +++ b/models/migrations/main.go @@ -9,12 +9,6 @@ import ( var Migrations = migrate.NewMigrations() -func init() { - if err := Migrations.DiscoverCaller(); err != nil { - panic(err) - } -} - func MigrateDatabase(ctx context.Context, sqlDB *bun.DB) error { migrator := migrate.NewMigrator(sqlDB, Migrations) err := migrator.Init(ctx) diff --git a/script/postgress/configmap.yaml b/script/postgress/configmap.yaml new file mode 100644 index 00000000..e1ba1c8c --- /dev/null +++ b/script/postgress/configmap.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: jiaozifs-api-postgres-config + labels: + app: jiaozifs-api-db-cfg + apptype: jiaozifs-pg-db +data: + POSTGRES_DB: "postgresdb" + POSTGRES_USER: "admin" + POSTGRES_PASSWORD: "psltest" diff --git a/script/postgress/deployment.yaml b/script/postgress/deployment.yaml new file mode 100644 index 00000000..1b22f99a --- /dev/null +++ b/script/postgress/deployment.yaml @@ -0,0 +1,34 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: jiaozifs-api-pg-db-deployment # Sets Deployment name + labels: + apptype: jiaozifs-pg-db +spec: + replicas: 1 + selector: + matchLabels: + app: jiaozifs-api-pg-db + template: + metadata: + labels: + app: jiaozifs-api-pg-db + apptype: jiaozifs-pg-db + spec: + nodeName: k2 + containers: + - name: postgres + image: postgres:13.13 + imagePullPolicy: "IfNotPresent" + ports: + - containerPort: 5432 + envFrom: + - configMapRef: + name: jiaozifs-api-postgres-config + volumeMounts: + - mountPath: /var/lib/postgresql/data + name: postgredb + volumes: + - name: postgredb + persistentVolumeClaim: + claimName: jiaozifs-postgres-pv-claim diff --git a/script/postgress/pv.yaml b/script/postgress/pv.yaml new file mode 100644 index 00000000..84da9251 --- /dev/null +++ b/script/postgress/pv.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: jiaozifs-postgres-pv-volume + labels: + type: local + apptype: jiaozifs-pg-db +spec: + storageClassName: manual + capacity: + storage: 5Gi + accessModes: + - ReadWriteOnce + hostPath: + path: "/data/jiaozifs-pg-data" diff --git a/script/postgress/pvc.yaml b/script/postgress/pvc.yaml new file mode 100644 index 00000000..808d4c68 --- /dev/null +++ b/script/postgress/pvc.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: jiaozifs-postgres-pv-claim + labels: + apptype: jiaozifs-pg-db +spec: + storageClassName: manual + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi diff --git a/script/postgress/svc.yaml b/script/postgress/svc.yaml new file mode 100644 index 00000000..f6a8fc27 --- /dev/null +++ b/script/postgress/svc.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: jiaozifs-api-db-service + labels: + apptype: jiaozifs-pg-db +spec: + type: ClusterIP + selector: + app: jiaozifs-api-pg-db + ports: + - protocol: TCP + port: 5432 + targetPort: 5432 diff --git a/script/start.sh b/script/start.sh new file mode 100755 index 00000000..e7ab43aa --- /dev/null +++ b/script/start.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +regex="--config[[:space:]]+([^[:space:]]+)" +if [[ $@ =~ $regex ]]; then + config_value="--config ${BASH_REMATCH[1]}" + echo "Config value: $config_value" +fi + +regex="--log-level[[:space:]]+([^[:space:]]+)" +if [[ $@ =~ $regex ]]; then + loglevel="--log-level ${BASH_REMATCH[1]}" + echo "Log level: $loglevel" +fi + +/jzfs init $@ + +/jzfs daemon $config_value $loglevel From 0967469bf19cda68158daa9bafe19f82691355e3 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Tue, 23 Jan 2024 12:28:09 +0800 Subject: [PATCH 158/210] feat: change home path --- charts/templates/deployment.yaml | 2 +- charts/values.yaml | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/charts/templates/deployment.yaml b/charts/templates/deployment.yaml index 3018839b..e9ede4ef 100644 --- a/charts/templates/deployment.yaml +++ b/charts/templates/deployment.yaml @@ -19,6 +19,6 @@ spec: - name: jiaozifs-backend image: gitdatateam/jzfs:latest imagePullPolicy: Always - args: ["--db {{ .Values.db }} --log-level {{ .Values.log_level }} --bs_path {{ .Values.bs_path }} --listen http://0.0.0.0:{{ .Values.port }} --config {{ .Values.config }}"] + args: ["--db {{ .Values.db }} --log-level {{ .Values.log_level }} --bs_path {{ .Values.home_path }}/data --listen http://0.0.0.0:{{ .Values.port }} --config {{ .Values.home_path }}/config.yaml"] ports: - containerPort: {{ .Values.port }} diff --git a/charts/values.yaml b/charts/values.yaml index 59b3eb91..bfbd7e96 100644 --- a/charts/values.yaml +++ b/charts/values.yaml @@ -3,8 +3,7 @@ # See README.md for details. replicas: 1 db: "" -bs_path: "/app/data" -config: "/app/config.toml" +home_path: "/app/data" port: 34913 ingress_name: nginx log_level: info From cb22d2a6098775311372837434971fcb3c9555aa Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Tue, 23 Jan 2024 13:58:10 +0800 Subject: [PATCH 159/210] feat: support bind to pvc --- charts/templates/deployment.yaml | 7 +++++++ charts/values.yaml | 1 + 2 files changed, 8 insertions(+) diff --git a/charts/templates/deployment.yaml b/charts/templates/deployment.yaml index e9ede4ef..a1169f6c 100644 --- a/charts/templates/deployment.yaml +++ b/charts/templates/deployment.yaml @@ -22,3 +22,10 @@ spec: args: ["--db {{ .Values.db }} --log-level {{ .Values.log_level }} --bs_path {{ .Values.home_path }}/data --listen http://0.0.0.0:{{ .Values.port }} --config {{ .Values.home_path }}/config.yaml"] ports: - containerPort: {{ .Values.port }} + volumeMounts: + - name: jiaozifs-home + mountPath: "/app" + volumes: + - name: jiaozifs-home + persistentVolumeClaim: + claimName: {{ .Values.claim_name }} \ No newline at end of file diff --git a/charts/values.yaml b/charts/values.yaml index bfbd7e96..5f47e0bc 100644 --- a/charts/values.yaml +++ b/charts/values.yaml @@ -7,3 +7,4 @@ home_path: "/app/data" port: 34913 ingress_name: nginx log_level: info +claim_name: jiaozifs-home" From 56daedd1770e4807ffcdc3f5eafbcc90f9f4bd3a Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Tue, 23 Jan 2024 14:04:27 +0800 Subject: [PATCH 160/210] fix: remove char --- charts/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/values.yaml b/charts/values.yaml index 5f47e0bc..cd9228a5 100644 --- a/charts/values.yaml +++ b/charts/values.yaml @@ -7,4 +7,4 @@ home_path: "/app/data" port: 34913 ingress_name: nginx log_level: info -claim_name: jiaozifs-home" +claim_name: jiaozifs-home From 09a0034ad5d64f34426fb70311d93e08a62442fe Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Tue, 23 Jan 2024 14:09:32 +0800 Subject: [PATCH 161/210] feat: use home dir --- charts/templates/deployment.yaml | 2 +- charts/values.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/templates/deployment.yaml b/charts/templates/deployment.yaml index a1169f6c..58aa4e71 100644 --- a/charts/templates/deployment.yaml +++ b/charts/templates/deployment.yaml @@ -28,4 +28,4 @@ spec: volumes: - name: jiaozifs-home persistentVolumeClaim: - claimName: {{ .Values.claim_name }} \ No newline at end of file + claimName: {{ .Values.claim_name }} diff --git a/charts/values.yaml b/charts/values.yaml index cd9228a5..7e83a60f 100644 --- a/charts/values.yaml +++ b/charts/values.yaml @@ -3,7 +3,7 @@ # See README.md for details. replicas: 1 db: "" -home_path: "/app/data" +home_path: "/app" port: 34913 ingress_name: nginx log_level: info From 4da0d7180763c387a03602ce7e304d4e7eb557ff Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Tue, 23 Jan 2024 16:19:32 +0800 Subject: [PATCH 162/210] feat: add deployment --- .github/workflows/basic_check.yml | 16 +++--- .github/workflows/deployment.yml | 58 +++++++++++++++++++++ .github/workflows/flow.yml | 22 ++++++++ .github/workflows/test.yml | 7 +-- {charts => chart}/Chart.yaml | 0 {charts => chart}/templates/deployment.yaml | 2 +- {charts => chart}/templates/ingress.yaml | 0 {charts => chart}/templates/service.yaml | 0 {charts => chart}/values.yaml | 1 + 9 files changed, 93 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/deployment.yml create mode 100644 .github/workflows/flow.yml rename {charts => chart}/Chart.yaml (100%) rename {charts => chart}/templates/deployment.yaml (94%) rename {charts => chart}/templates/ingress.yaml (100%) rename {charts => chart}/templates/service.yaml (100%) rename {charts => chart}/values.yaml (95%) diff --git a/.github/workflows/basic_check.yml b/.github/workflows/basic_check.yml index d4907058..9716bdfa 100644 --- a/.github/workflows/basic_check.yml +++ b/.github/workflows/basic_check.yml @@ -1,12 +1,7 @@ name: basic-check on: - push: - branches: - - master - pull_request: - branches: - - '**' + workflow_call: jobs: check: @@ -35,6 +30,15 @@ jobs: - name: end-of-file-check uses: njgibbon/fend@main + - uses: azure/setup-helm@v3 + with: + version: 'v3.14.0' + id: install + + - name: Helm lint + run: | + helm lint --strict chart + - name: Lint run: | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -d -b $(go env GOPATH)/bin v1.55.1 diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml new file mode 100644 index 00000000..7b36feef --- /dev/null +++ b/.github/workflows/deployment.yml @@ -0,0 +1,58 @@ +name: test + +on: + workflow_call: + secrets: + DOCKERHUB_USERNAME: + required: true + DOCKERHUB_TOKEN: + required: true + KUBECONTENT: + required: true +jobs: + deployment: + if: ${{ startsWith(github.ref, 'refs/heads/deployment/') || github.ref == 'refs/heads/master' }} + runs-on: [self-hosted] + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.20.9' + cache: true + + - name: install deps + run: | + sudo apt-get update + sudo apt-get -o Acquire::Retries=3 install make gcc git curl wget -y + + - name: Build + env: + GOPROXY: "https://proxy.golang.org,direct" + GO111MODULE: "on" + run: | + make build + + - name: Get version + id: version + run: | + if [[ -n "$GITHUB_REF" && "$GITHUB_REF" == "refs/tags/"* ]]; then + echo "tag version" + echo "::set-output name=version::${GITHUB_REF/refs\/tags\//}" + else + echo "commit version" + echo "::set-output name=version::${{ github.sha }}" + fi + + - name: Build and push + run: | + docker login -u ${{ secrets.DOCKERHUB_USERNAME }} -p ${{ secrets.DOCKERHUB_TOKEN }} + make docker TAG=${{ steps.version.outputs.version }} + + - name: Deploy + uses: WyriHaximus/github-action-helm3@v3 + with: + exec: helm upgrade jiaozifs-api --install ./chart --wait --atomic --timeout 2m --namespace jiaozifs --set-string log_level=debug --set-string db='${{secrets.DBURL}}' --set-string tag=${{ steps.version.outputs.version }} + kubeconfig: ${{ secrets.KUBECONTENT }} + overrule_existing_kubeconfig: "true" diff --git a/.github/workflows/flow.yml b/.github/workflows/flow.yml new file mode 100644 index 00000000..c71233a9 --- /dev/null +++ b/.github/workflows/flow.yml @@ -0,0 +1,22 @@ +name: test + +on: + push: + branches: + - main + - 'deployment/**' + pull_request: + branches: + - '**' + +jobs: + check: + uses: ./.github/workflows/basic_check.yml + test: + uses: ./.github/workflows/test.yml + deployment: + needs: + - test + - check + uses: ./.github/workflows/deployment.yml + secrets: inherit diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 77abde4e..b1de9a03 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,12 +1,7 @@ name: test on: - push: - branches: - - master - pull_request: - branches: - - '**' + workflow_call: jobs: test: diff --git a/charts/Chart.yaml b/chart/Chart.yaml similarity index 100% rename from charts/Chart.yaml rename to chart/Chart.yaml diff --git a/charts/templates/deployment.yaml b/chart/templates/deployment.yaml similarity index 94% rename from charts/templates/deployment.yaml rename to chart/templates/deployment.yaml index 58aa4e71..babd7bed 100644 --- a/charts/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -17,7 +17,7 @@ spec: spec: containers: - name: jiaozifs-backend - image: gitdatateam/jzfs:latest + image: gitdatateam/jzfs:{{ .Values.tag }} imagePullPolicy: Always args: ["--db {{ .Values.db }} --log-level {{ .Values.log_level }} --bs_path {{ .Values.home_path }}/data --listen http://0.0.0.0:{{ .Values.port }} --config {{ .Values.home_path }}/config.yaml"] ports: diff --git a/charts/templates/ingress.yaml b/chart/templates/ingress.yaml similarity index 100% rename from charts/templates/ingress.yaml rename to chart/templates/ingress.yaml diff --git a/charts/templates/service.yaml b/chart/templates/service.yaml similarity index 100% rename from charts/templates/service.yaml rename to chart/templates/service.yaml diff --git a/charts/values.yaml b/chart/values.yaml similarity index 95% rename from charts/values.yaml rename to chart/values.yaml index 7e83a60f..f4b75418 100644 --- a/charts/values.yaml +++ b/chart/values.yaml @@ -8,3 +8,4 @@ port: 34913 ingress_name: nginx log_level: info claim_name: jiaozifs-home +tag: latest From 8ff1d730372232343ab7e156a36ea0211ce60cee Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Wed, 24 Jan 2024 15:00:36 +0800 Subject: [PATCH 163/210] feat: change default branch --- .github/workflows/deployment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 7b36feef..b48a80ef 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -11,7 +11,7 @@ on: required: true jobs: deployment: - if: ${{ startsWith(github.ref, 'refs/heads/deployment/') || github.ref == 'refs/heads/master' }} + if: ${{ startsWith(github.ref, 'refs/heads/deployment/') || github.ref == 'refs/heads/main' }} runs-on: [self-hosted] steps: - uses: actions/checkout@v4 From 88cf542c40ea2b48aec0d0b12c9f1a3847b63388 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Wed, 24 Jan 2024 19:53:57 +0800 Subject: [PATCH 164/210] fix: deploy trigger --- .github/workflows/flow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/flow.yml b/.github/workflows/flow.yml index c71233a9..940124d8 100644 --- a/.github/workflows/flow.yml +++ b/.github/workflows/flow.yml @@ -1,14 +1,14 @@ name: test on: + create: push: branches: - main - - 'deployment/**' + - deployment/** pull_request: branches: - '**' - jobs: check: uses: ./.github/workflows/basic_check.yml From 4aab0cf7c7ead6ff73fc346aef19442955453b1a Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Wed, 24 Jan 2024 20:47:48 +0800 Subject: [PATCH 165/210] Fix/dup trigger deploy (#118) * fix: remove create event, this event have include in push to deployment * feat: disable cache to provent at post setup --- .github/workflows/deployment.yml | 2 +- .github/workflows/flow.yml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index b48a80ef..a30ca543 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -20,7 +20,7 @@ jobs: uses: actions/setup-go@v4 with: go-version: '1.20.9' - cache: true + cache: false #post setup fail in local runner , disable for now - name: install deps run: | diff --git a/.github/workflows/flow.yml b/.github/workflows/flow.yml index 940124d8..45638bff 100644 --- a/.github/workflows/flow.yml +++ b/.github/workflows/flow.yml @@ -1,7 +1,6 @@ name: test on: - create: push: branches: - main From 40474436362f74fbbdccf530e44ef6cb1b7eb9d5 Mon Sep 17 00:00:00 2001 From: zjy Date: Thu, 25 Jan 2024 10:07:06 +0800 Subject: [PATCH 166/210] fix: fix upload file path error --- controller/validator/validate.go | 2 +- controller/validator/validate_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/controller/validator/validate.go b/controller/validator/validate.go index 7e855b34..da67f85f 100644 --- a/controller/validator/validate.go +++ b/controller/validator/validate.go @@ -12,7 +12,7 @@ var ( ReValidRef = regexp.MustCompile(`^\w+/?\w+$`) ReValidRepo = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_\-]{1,61}[a-zA-Z0-9]$`) ReValidUser = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_-]{1,28}[a-zA-Z0-9]$`) - ReValidPath = regexp.MustCompile(`^(?:[^\x00-\x1F\\/:*?"<>|]+/)*[^\x00-\x1F\\/:*?"<>|]+$`) + ReValidPath = regexp.MustCompile(`^(?:/?[^\x00-\x1F\\/:*?"<>|]+/)*[^\x00-\x1F\\/:*?"<>|]+(\.[^\x00-\x1F\\/:*?"<>|]+)?$`) // RepoNameBlackList forbid repo name, reserve for routes RepoNameBlackList = []string{"repository", "repositories", "wip", "wips", "object", "objects", "commit", "commits", "ref", "refs", "repo", "repos", "user", "users"} diff --git a/controller/validator/validate_test.go b/controller/validator/validate_test.go index fe7b182e..776fcb0e 100644 --- a/controller/validator/validate_test.go +++ b/controller/validator/validate_test.go @@ -98,7 +98,7 @@ func TestValidateUsername(t *testing.T) { func TestValidateObjectPath(t *testing.T) { //Validate Obj Path - validObjectPaths := []string{"path/to/object", "file.txt", "folder/file.txt", "我的图片.png", "我的文件/我的应用.exe", "私のビデオ.mp3"} + validObjectPaths := []string{"path/to/object", "file.txt", "folder/file.txt", "我的图片.png", "我的文件/我的应用.exe", "私のビデオ.mp3, /video.mp3, /path/pic.png"} for _, path := range validObjectPaths { err := ValidateObjectPath(path) if err != nil { From e24db75b5de8a92ee7953064d0aa3b255e021bd6 Mon Sep 17 00:00:00 2001 From: zjy Date: Thu, 25 Jan 2024 14:09:14 +0800 Subject: [PATCH 167/210] fix: the file path must start with / --- controller/validator/validate.go | 2 +- controller/validator/validate_test.go | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/controller/validator/validate.go b/controller/validator/validate.go index da67f85f..8b6be94e 100644 --- a/controller/validator/validate.go +++ b/controller/validator/validate.go @@ -12,7 +12,7 @@ var ( ReValidRef = regexp.MustCompile(`^\w+/?\w+$`) ReValidRepo = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_\-]{1,61}[a-zA-Z0-9]$`) ReValidUser = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_-]{1,28}[a-zA-Z0-9]$`) - ReValidPath = regexp.MustCompile(`^(?:/?[^\x00-\x1F\\/:*?"<>|]+/)*[^\x00-\x1F\\/:*?"<>|]+(\.[^\x00-\x1F\\/:*?"<>|]+)?$`) + ReValidPath = regexp.MustCompile(`^/(?:[^\x00-\x1F\\/:*?"<>|]+/)*[^\x00-\x1F\\/:*?"<>|]+(\.[^\x00-\x1F\\/:*?"<>|]+)?$`) // RepoNameBlackList forbid repo name, reserve for routes RepoNameBlackList = []string{"repository", "repositories", "wip", "wips", "object", "objects", "commit", "commits", "ref", "refs", "repo", "repos", "user", "users"} diff --git a/controller/validator/validate_test.go b/controller/validator/validate_test.go index 776fcb0e..c7f870a3 100644 --- a/controller/validator/validate_test.go +++ b/controller/validator/validate_test.go @@ -98,7 +98,7 @@ func TestValidateUsername(t *testing.T) { func TestValidateObjectPath(t *testing.T) { //Validate Obj Path - validObjectPaths := []string{"path/to/object", "file.txt", "folder/file.txt", "我的图片.png", "我的文件/我的应用.exe", "私のビデオ.mp3, /video.mp3, /path/pic.png"} + validObjectPaths := []string{"/path/to/object", "/file.txt", "/folder/file.txt", "/我的图片.png", "/我的文件/我的应用.exe", "/私のビデオ.mp3"} for _, path := range validObjectPaths { err := ValidateObjectPath(path) if err != nil { @@ -111,11 +111,12 @@ func TestValidateObjectPath(t *testing.T) { path string error string }{ - {"path/with/null\x00character", "invalid object path: it must not contain null characters or NTFS forbidden characters"}, - {"path/with/invalid/characters/:", "invalid object path: it must not contain null characters or NTFS forbidden characters"}, - {"path/with/invalid/characters/*", "invalid object path: it must not contain null characters or NTFS forbidden characters"}, - {"path/with/invalid/characters/\"", "invalid object path: it must not contain null characters or NTFS forbidden characters"}, - {"path/with/invalid/characters/ Date: Thu, 25 Jan 2024 14:26:19 +0800 Subject: [PATCH 168/210] Revert "fix: the file path must start with /" This reverts commit 7a05e2697f901faf96e85a9f72c7d516d4cc4cff. --- controller/validator/validate.go | 2 +- controller/validator/validate_test.go | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/controller/validator/validate.go b/controller/validator/validate.go index 8b6be94e..da67f85f 100644 --- a/controller/validator/validate.go +++ b/controller/validator/validate.go @@ -12,7 +12,7 @@ var ( ReValidRef = regexp.MustCompile(`^\w+/?\w+$`) ReValidRepo = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_\-]{1,61}[a-zA-Z0-9]$`) ReValidUser = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_-]{1,28}[a-zA-Z0-9]$`) - ReValidPath = regexp.MustCompile(`^/(?:[^\x00-\x1F\\/:*?"<>|]+/)*[^\x00-\x1F\\/:*?"<>|]+(\.[^\x00-\x1F\\/:*?"<>|]+)?$`) + ReValidPath = regexp.MustCompile(`^(?:/?[^\x00-\x1F\\/:*?"<>|]+/)*[^\x00-\x1F\\/:*?"<>|]+(\.[^\x00-\x1F\\/:*?"<>|]+)?$`) // RepoNameBlackList forbid repo name, reserve for routes RepoNameBlackList = []string{"repository", "repositories", "wip", "wips", "object", "objects", "commit", "commits", "ref", "refs", "repo", "repos", "user", "users"} diff --git a/controller/validator/validate_test.go b/controller/validator/validate_test.go index c7f870a3..776fcb0e 100644 --- a/controller/validator/validate_test.go +++ b/controller/validator/validate_test.go @@ -98,7 +98,7 @@ func TestValidateUsername(t *testing.T) { func TestValidateObjectPath(t *testing.T) { //Validate Obj Path - validObjectPaths := []string{"/path/to/object", "/file.txt", "/folder/file.txt", "/我的图片.png", "/我的文件/我的应用.exe", "/私のビデオ.mp3"} + validObjectPaths := []string{"path/to/object", "file.txt", "folder/file.txt", "我的图片.png", "我的文件/我的应用.exe", "私のビデオ.mp3, /video.mp3, /path/pic.png"} for _, path := range validObjectPaths { err := ValidateObjectPath(path) if err != nil { @@ -111,12 +111,11 @@ func TestValidateObjectPath(t *testing.T) { path string error string }{ - {"path/with/null", "invalid object path: it must not contain null characters or NTFS forbidden characters"}, - {"/path/with/null\x00character", "invalid object path: it must not contain null characters or NTFS forbidden characters"}, - {"/path/with/invalid/characters/:", "invalid object path: it must not contain null characters or NTFS forbidden characters"}, - {"/path/with/invalid/characters/*", "invalid object path: it must not contain null characters or NTFS forbidden characters"}, - {"/path/with/invalid/characters/\"", "invalid object path: it must not contain null characters or NTFS forbidden characters"}, - {"/path/with/invalid/characters/ Date: Fri, 26 Jan 2024 11:12:31 +0800 Subject: [PATCH 169/210] fix: fix obj path start with '/' --- controller/validator/validate.go | 2 +- controller/validator/validate_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/controller/validator/validate.go b/controller/validator/validate.go index da67f85f..041c5246 100644 --- a/controller/validator/validate.go +++ b/controller/validator/validate.go @@ -12,7 +12,7 @@ var ( ReValidRef = regexp.MustCompile(`^\w+/?\w+$`) ReValidRepo = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_\-]{1,61}[a-zA-Z0-9]$`) ReValidUser = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_-]{1,28}[a-zA-Z0-9]$`) - ReValidPath = regexp.MustCompile(`^(?:/?[^\x00-\x1F\\/:*?"<>|]+/)*[^\x00-\x1F\\/:*?"<>|]+(\.[^\x00-\x1F\\/:*?"<>|]+)?$`) + ReValidPath = regexp.MustCompile(`^[^\x00/:*?"<>|]*/?([^/\s\x00:*?"<>|]+/)*[^/\s\x00:*?"<>|]+(?:\.[a-zA-Z0-9]+)?$`) // RepoNameBlackList forbid repo name, reserve for routes RepoNameBlackList = []string{"repository", "repositories", "wip", "wips", "object", "objects", "commit", "commits", "ref", "refs", "repo", "repos", "user", "users"} diff --git a/controller/validator/validate_test.go b/controller/validator/validate_test.go index 776fcb0e..4bfaf4f8 100644 --- a/controller/validator/validate_test.go +++ b/controller/validator/validate_test.go @@ -98,7 +98,7 @@ func TestValidateUsername(t *testing.T) { func TestValidateObjectPath(t *testing.T) { //Validate Obj Path - validObjectPaths := []string{"path/to/object", "file.txt", "folder/file.txt", "我的图片.png", "我的文件/我的应用.exe", "私のビデオ.mp3, /video.mp3, /path/pic.png"} + validObjectPaths := []string{"path/to/object", "file.txt", "folder/file.txt", "我的图片.png", "我的文件/我的应用.exe", "私のビデオ.mp3", "/video.mp3", "/path/pic.png"} for _, path := range validObjectPaths { err := ValidateObjectPath(path) if err != nil { From e702a56a8a4a4d02d0db200c6ea429e015179be2 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Thu, 22 Feb 2024 14:38:00 +0800 Subject: [PATCH 170/210] feat: support ipfs --- block/ipfs/adapter.go | 408 ++++++++++++++++++++++++++++++++++++++++++ block/ipfs/walker.go | 96 ++++++++++ 2 files changed, 504 insertions(+) create mode 100644 block/ipfs/adapter.go create mode 100644 block/ipfs/walker.go diff --git a/block/ipfs/adapter.go b/block/ipfs/adapter.go new file mode 100644 index 00000000..4eaa0346 --- /dev/null +++ b/block/ipfs/adapter.go @@ -0,0 +1,408 @@ +package ipfs + +import ( + "context" + "crypto/md5" //nolint:gosec + "encoding/hex" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "path" + "strconv" + "strings" + "time" + + "github.com/modern-go/reflect2" + + "github.com/ipfs/kubo/core/coreiface/options" + + "github.com/ipfs/boxo/files" + + "github.com/google/uuid" + "github.com/ipfs/kubo/client/rpc" + "github.com/jiaozifs/jiaozifs/block" + "github.com/jiaozifs/jiaozifs/utils/hash" + ma "github.com/multiformats/go-multiaddr" +) + +const DefaultNamespacePrefix = block.BlockstoreIPFS + "://" + +type Adapter struct { + url string + client *rpc.HttpApi + removeEmptyDir bool +} + +var ( + ErrPathNotWritable = errors.New("path provided is not writable") + ErrInvalidUploadIDFormat = errors.New("invalid upload id format") + ErrBadPath = errors.New("bad path traversal blocked") + ErrInvalidStorageNamespace = errors.New("invalid storageNamespace") +) + +type QualifiedKey struct { + block.CommonQualifiedKey + url string +} + +func (qk QualifiedKey) Format() string { + p := path.Join(qk.url, qk.GetStorageNamespace(), qk.GetKey()) + return qk.GetStorageType().Scheme() + "://" + p +} + +func (qk QualifiedKey) GetStorageType() block.StorageType { + return qk.CommonQualifiedKey.GetStorageType() +} + +func (qk QualifiedKey) GetStorageNamespace() string { + return qk.CommonQualifiedKey.GetStorageNamespace() +} + +func (qk QualifiedKey) GetKey() string { + return qk.CommonQualifiedKey.GetKey() +} + +func NewAdapter(url string, opts ...func(a *Adapter)) (*Adapter, error) { + addr, err := ma.NewMultiaddr(strings.TrimSpace(string(url))) + if err != nil { + return nil, err + } + + client, err := rpc.NewApi(addr) + if err != nil { + return nil, err + } + + localAdapter := &Adapter{ + url: url, + client: client, + removeEmptyDir: true, + } + for _, opt := range opts { + opt(localAdapter) + } + return localAdapter, nil +} + +func (l *Adapter) GetPreSignedURL(_ context.Context, _ block.ObjectPointer, _ block.PreSignMode) (string, time.Time, error) { + return "", time.Time{}, fmt.Errorf("ipfs adapter presigned URL: %w", block.ErrOperationNotSupported) +} + +func (l *Adapter) extractParamsFromObj(ptr block.ObjectPointer) (string, string, error) { + if strings.HasPrefix(ptr.Identifier, DefaultNamespacePrefix) { + // check abs path + p := ptr.Identifier[len(DefaultNamespacePrefix):] + seqs := strings.SplitN(p, "/", 2) + if len(seqs) != 2 { + return "", "", fmt.Errorf("seqs must start with namespace") + } + return seqs[0], seqs[1], nil + } + + if !strings.HasPrefix(ptr.StorageNamespace, DefaultNamespacePrefix) { + return "", "", fmt.Errorf("%w: storage namespace", ErrBadPath) + } + return ptr.StorageNamespace[len(DefaultNamespacePrefix):], ptr.Identifier, nil +} + +func (l *Adapter) extractNamespace(storageNamespace string) (string, error) { + if !strings.HasPrefix(storageNamespace, DefaultNamespacePrefix) { + return "", fmt.Errorf("%w: storage namespace", ErrBadPath) + } + return storageNamespace[len(DefaultNamespacePrefix):], nil +} + +func (l *Adapter) ensureNamespace(ctx context.Context, namespace string) error { + err := l.client.Unixfs().Mkdir(ctx, "/"+namespace) + if !reflect2.IsNil(err) { + if !strings.Contains(err.Error(), "file already exists") { + return err + } + } + return nil +} + +func (l *Adapter) Put(ctx context.Context, obj block.ObjectPointer, _ int64, reader io.Reader, _ block.PutOpts) error { + namespace, identify, err := l.extractParamsFromObj(obj) + if err != nil { + return err + } + + err = l.ensureNamespace(ctx, namespace) + if err != nil { + return err + } + + ipfsPath, err := l.client.Unixfs().Add(ctx, files.NewReaderFile(reader)) + if !reflect2.IsNil(err) { + return err + } + //copy to mfs + err = l.client.Unixfs().Cp(ctx, ipfsPath.String(), fullPath(namespace, identify), options.Unixfs.CpParents(true)) + if !reflect2.IsNil(err) { + if strings.Contains(err.Error(), "already has entry") { // already exists + return nil + } + return err + } + return nil +} + +func (l *Adapter) Remove(ctx context.Context, obj block.ObjectPointer) error { + namespace, identify, err := l.extractParamsFromObj(obj) + if err != nil { + return err + } + + err = l.client.Unixfs().Rm(ctx, fullPath(namespace, identify)) + if !reflect2.IsNil(err) { + return err + } + return nil +} + +func (l *Adapter) RemoveNameSpace(ctx context.Context, storageNamespace string) error { + namespace, err := l.extractNamespace(storageNamespace) + if err != nil { + return err + } + + err = l.client.Unixfs().Rm(ctx, fmt.Sprintf("/%s", namespace), options.Unixfs.Recursive(true), options.Unixfs.Force(true)) + if !reflect2.IsNil(err) { + return err + } + return nil +} + +func (l *Adapter) Copy(ctx context.Context, sourceObj, destinationObj block.ObjectPointer) error { + namespace, _, err := l.extractParamsFromObj(sourceObj) + if err != nil { + return err + } + + err = l.client.Unixfs().Cp(ctx, + fmt.Sprintf("/%s/%s", namespace, sourceObj.Identifier), + fmt.Sprintf("/%s/%s", namespace, destinationObj.Identifier), + options.Unixfs.CpParents(true), + ) + if !reflect2.IsNil(err) { + return err + } + return nil +} + +func (l *Adapter) UploadCopyPart(ctx context.Context, sourceObj, destinationObj block.ObjectPointer, uploadID string, partNumber int) (*block.UploadPartResponse, error) { + if err := isValidUploadID(uploadID); err != nil { + return nil, err + } + r, err := l.Get(ctx, sourceObj, 0) + if err != nil { + return nil, err + } + md5Read := hash.NewHashingReader(r, hash.Md5) + fName := uploadID + fmt.Sprintf("-%05d", partNumber) + err = l.Put(ctx, block.ObjectPointer{StorageNamespace: destinationObj.StorageNamespace, Identifier: fName}, -1, md5Read, block.PutOpts{}) + if err != nil { + return nil, err + } + etag := hex.EncodeToString(md5Read.Md5.Sum(nil)) + return &block.UploadPartResponse{ + ETag: etag, + }, nil +} + +func (l *Adapter) UploadCopyPartRange(ctx context.Context, sourceObj, destinationObj block.ObjectPointer, uploadID string, partNumber int, startPosition, endPosition int64) (*block.UploadPartResponse, error) { + if err := isValidUploadID(uploadID); err != nil { + return nil, err + } + r, err := l.GetRange(ctx, sourceObj, startPosition, endPosition) + if err != nil { + return nil, err + } + md5Read := hash.NewHashingReader(r, hash.Md5) + fName := uploadID + fmt.Sprintf("-%05d", partNumber) + err = l.Put(ctx, block.ObjectPointer{StorageNamespace: destinationObj.StorageNamespace, Identifier: fName}, -1, md5Read, block.PutOpts{}) + if err != nil { + return nil, err + } + etag := hex.EncodeToString(md5Read.Md5.Sum(nil)) + return &block.UploadPartResponse{ + ETag: etag, + }, err +} + +func (l *Adapter) Get(ctx context.Context, obj block.ObjectPointer, _ int64) (io.ReadCloser, error) { + namespace, identify, err := l.extractParamsFromObj(obj) + if err != nil { + return nil, err + } + + rc, err := l.client.Unixfs().Read(ctx, fmt.Sprintf("/%s/%s", namespace, identify)) + if !reflect2.IsNil(err) { + return nil, err + } + return rc, nil +} + +func (l *Adapter) GetWalker(uri *url.URL) (block.Walker, error) { + if err := block.ValidateStorageType(uri, block.StorageTypeLocal); err != nil { + return nil, err + } + + return NewIPFSWalker(l.client), nil +} + +func (l *Adapter) Exists(ctx context.Context, obj block.ObjectPointer) (bool, error) { + namespace, identify, err := l.extractParamsFromObj(obj) + if err != nil { + return false, err + } + + _, err = l.client.Unixfs().Stat(ctx, fmt.Sprintf("/%s/%s", namespace, identify)) + if !reflect2.IsNil(err) { + return false, err + } + return true, nil +} + +func (l *Adapter) GetRange(ctx context.Context, obj block.ObjectPointer, start int64, end int64) (io.ReadCloser, error) { + if start < 0 || end < start { + return nil, block.ErrBadIndex + } + namespace, identify, err := l.extractParamsFromObj(obj) + if err != nil { + return nil, err + } + rc, err := l.client.Unixfs().Read(ctx, fmt.Sprintf("/%s/%s", namespace, identify), options.Unixfs.Offset(start), options.Unixfs.Count(end-start)) + if !reflect2.IsNil(err) { + return nil, err + } + return rc, nil +} + +func (l *Adapter) GetProperties(_ context.Context, obj block.ObjectPointer) (block.Properties, error) { + return block.Properties{}, nil +} + +func (l *Adapter) CreateMultiPartUpload(ctx context.Context, obj block.ObjectPointer, _ *http.Request, _ block.CreateMultiPartUploadOpts) (*block.CreateMultiPartUploadResponse, error) { + if strings.Contains(obj.Identifier, "/") { + namespace, _, err := l.extractParamsFromObj(obj) + if err != nil { + return nil, err + } + err = l.ensureNamespace(ctx, namespace) + if err != nil { + return nil, err + } + } + uidBytes := uuid.New() + uploadID := hex.EncodeToString(uidBytes[:]) + return &block.CreateMultiPartUploadResponse{ + UploadID: uploadID, + }, nil +} + +func (l *Adapter) UploadPart(ctx context.Context, obj block.ObjectPointer, _ int64, reader io.Reader, uploadID string, partNumber int) (*block.UploadPartResponse, error) { + if err := isValidUploadID(uploadID); err != nil { + return nil, err + } + md5Read := hash.NewHashingReader(reader, hash.Md5) + fName := uploadID + fmt.Sprintf("-%05d", partNumber) + err := l.Put(ctx, block.ObjectPointer{StorageNamespace: obj.StorageNamespace, Identifier: fName}, -1, md5Read, block.PutOpts{}) + etag := hex.EncodeToString(md5Read.Md5.Sum(nil)) + return &block.UploadPartResponse{ + ETag: etag, + }, err +} + +func (l *Adapter) AbortMultiPartUpload(_ context.Context, obj block.ObjectPointer, uploadID string) error { + if err := isValidUploadID(uploadID); err != nil { + return err + } + + panic("todo") +} + +func (l *Adapter) CompleteMultiPartUpload(ctx context.Context, obj block.ObjectPointer, uploadID string, multipartList *block.MultipartUploadCompletion) (*block.CompleteMultiPartUploadResponse, error) { + if err := isValidUploadID(uploadID); err != nil { + return nil, err + } + etag := computeETag(multipartList.Part) + "-" + strconv.Itoa(len(multipartList.Part)) + + size, err := l.unitePartFiles(obj, uploadID) + if err != nil { + return nil, fmt.Errorf("multipart upload unite for %s: %w", uploadID, err) + } + return &block.CompleteMultiPartUploadResponse{ + ETag: etag, + ContentLength: size, + }, nil +} + +func computeETag(parts []block.MultipartPart) string { + var etagHex []string + for _, p := range parts { + e := strings.Trim(p.ETag, `"`) + etagHex = append(etagHex, e) + } + s := strings.Join(etagHex, "") + b, _ := hex.DecodeString(s) + md5res := md5.Sum(b) //nolint:gosec + csm := hex.EncodeToString(md5res[:]) + return csm +} + +func (l *Adapter) unitePartFiles(identifier block.ObjectPointer, uploadID string) (int64, error) { + panic("not impl") +} + +func (l *Adapter) removePartFiles(ctx context.Context, files []string) error { + var firstErr error + for _, name := range files { + // If removal fails prefer to skip the error: "only" wasted space. + _ = l.client.Unixfs().Rm(ctx, name, options.Unixfs.Force(true)) + } + return firstErr +} + +func (l *Adapter) BlockstoreType() string { + return block.BlockstoreIPFS +} + +func (l *Adapter) GetStorageNamespaceInfo() block.StorageNamespaceInfo { + info := block.DefaultStorageNamespaceInfo(block.BlockstoreIPFS) + info.PreSignSupport = false + info.DefaultNamespacePrefix = DefaultNamespacePrefix + info.ImportSupport = true + return info +} + +func (l *Adapter) ResolveNamespace(storageNamespace, key string, identifierType block.IdentifierType) (block.QualifiedKey, error) { + qk, err := block.DefaultResolveNamespace(storageNamespace, key, identifierType) + if err != nil { + return nil, err + } + + return QualifiedKey{ + CommonQualifiedKey: qk, + url: l.url, + }, nil +} + +func (l *Adapter) RuntimeStats() map[string]string { + return nil +} + +func isValidUploadID(uploadID string) error { + _, err := hex.DecodeString(uploadID) + if err != nil { + return fmt.Errorf("%w: %s", ErrInvalidUploadIDFormat, err) + } + return nil +} + +func fullPath(namespace, identify string) string { + return fmt.Sprintf("/%s/%s", namespace, identify) +} diff --git a/block/ipfs/walker.go b/block/ipfs/walker.go new file mode 100644 index 00000000..15a27b16 --- /dev/null +++ b/block/ipfs/walker.go @@ -0,0 +1,96 @@ +package ipfs + +import ( + "context" + "fmt" + "github.com/modern-go/reflect2" + "net/url" + "sort" + "strings" + + iface "github.com/ipfs/kubo/core/coreiface" + + "github.com/ipfs/kubo/core/coreiface/options" + + "github.com/ipfs/boxo/path" + + "github.com/ipfs/kubo/client/rpc" + "github.com/jiaozifs/jiaozifs/block" +) + +type Walker struct { + client *rpc.HttpApi + mark block.Mark +} + +func NewIPFSWalker(client *rpc.HttpApi) *Walker { + return &Walker{ + client: client, + mark: block.Mark{HasMore: true}, + } +} + +func (s *Walker) Walk(ctx context.Context, storageURI *url.URL, op block.WalkOptions, walkFn func(e block.ObjectStoreEntry) error) error { + const maxKeys = 1000 + prefix := strings.TrimLeft(storageURI.Path, "/") + + curPath, err := path.NewPath(prefix) + if err != nil { + return err + } + resultCh, err := s.client.Unixfs().Ls(ctx, curPath, options.Unixfs.ResolveChildren(true)) + if !reflect2.IsNil(err) { + return err + } + + entries, err := s.getAllEntries(resultCh, prefix) + if err != nil { + return err + } + + startIndex := sort.Search(len(entries), func(i int) bool { + return entries[i].FullKey > op.ContinuationToken && entries[i].FullKey > op.After + }) + for i := startIndex; i < len(entries); i++ { + err := walkFn(entries[i]) + if err != nil { + return err + } + s.mark.LastKey = entries[i].FullKey + s.mark.ContinuationToken = entries[i].FullKey + } + + s.mark = block.Mark{ + LastKey: "", + HasMore: false, + } + return nil +} +func (s *Walker) getAllEntries(ch <-chan iface.DirEntry, prefix string) ([]block.ObjectStoreEntry, error) { + var entries []block.ObjectStoreEntry + for record := range ch { + if record.Type == iface.TFile { + addr := fmt.Sprintf("ipfs://%s/%s", prefix, record.Name) + ent := block.ObjectStoreEntry{ + FullKey: fmt.Sprintf("%s/%s", prefix, record.Name), + RelativeKey: record.Name, + Address: addr, + Size: int64(record.Size), + } + entries = append(entries, ent) + } + } + + sort.Slice(entries, func(i, j int) bool { + return entries[i].FullKey < entries[j].FullKey + }) + return entries, nil +} + +func (s *Walker) Marker() block.Mark { + return s.mark +} + +func (s *Walker) GetSkippedEntries() []block.ObjectStoreEntry { + return nil +} From e5a6c01b154e4a7fc6ae898c0604d3423be4da11 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Thu, 22 Feb 2024 14:38:51 +0800 Subject: [PATCH 171/210] feat: support ipfs --- api/jiaozifs.gen.go | 104 ++++--- api/swagger.yml | 5 + block/adapter.go | 1 + block/factory/build.go | 20 ++ block/params/block.go | 5 + config/blockstore.go | 9 + controller/repository_ctl.go | 21 +- go.mod | 134 ++++++--- go.sum | 527 +++++++++++++++++++++++++++++++---- 9 files changed, 705 insertions(+), 121 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 9c0529cf..db863f8f 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -442,6 +442,11 @@ type UploadObjectParams struct { Path string `form:"path" json:"path"` } +// DeleteRepositoryParams defines parameters for DeleteRepository. +type DeleteRepositoryParams struct { + IsCleanData *bool `form:"is_clean_data,omitempty" json:"is_clean_data,omitempty"` +} + // DeleteBranchParams defines parameters for DeleteBranch. type DeleteBranchParams struct { RefName string `form:"refName" json:"refName"` @@ -714,7 +719,7 @@ type ClientInterface interface { UploadObjectWithBody(ctx context.Context, owner string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) // DeleteRepository request - DeleteRepository(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) + DeleteRepository(ctx context.Context, owner string, repository string, params *DeleteRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) // GetRepository request GetRepository(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -982,8 +987,8 @@ func (c *Client) UploadObjectWithBody(ctx context.Context, owner string, reposit return c.Client.Do(req) } -func (c *Client) DeleteRepository(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteRepositoryRequest(c.Server, owner, repository) +func (c *Client) DeleteRepository(ctx context.Context, owner string, repository string, params *DeleteRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteRepositoryRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -2081,7 +2086,7 @@ func NewUploadObjectRequestWithBody(server string, owner string, repository stri } // NewDeleteRepositoryRequest generates requests for DeleteRepository -func NewDeleteRepositoryRequest(server string, owner string, repository string) (*http.Request, error) { +func NewDeleteRepositoryRequest(server string, owner string, repository string, params *DeleteRepositoryParams) (*http.Request, error) { var err error var pathParam0 string @@ -2113,6 +2118,28 @@ func NewDeleteRepositoryRequest(server string, owner string, repository string) return nil, err } + if params != nil { + queryValues := queryURL.Query() + + if params.IsCleanData != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "is_clean_data", runtime.ParamLocationQuery, *params.IsCleanData); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + req, err := http.NewRequest("DELETE", queryURL.String(), nil) if err != nil { return nil, err @@ -3703,7 +3730,7 @@ type ClientWithResponsesInterface interface { UploadObjectWithBodyWithResponse(ctx context.Context, owner string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) // DeleteRepositoryWithResponse request - DeleteRepositoryWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*DeleteRepositoryResponse, error) + DeleteRepositoryWithResponse(ctx context.Context, owner string, repository string, params *DeleteRepositoryParams, reqEditors ...RequestEditorFn) (*DeleteRepositoryResponse, error) // GetRepositoryWithResponse request GetRepositoryWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*GetRepositoryResponse, error) @@ -4725,8 +4752,8 @@ func (c *ClientWithResponses) UploadObjectWithBodyWithResponse(ctx context.Conte } // DeleteRepositoryWithResponse request returning *DeleteRepositoryResponse -func (c *ClientWithResponses) DeleteRepositoryWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*DeleteRepositoryResponse, error) { - rsp, err := c.DeleteRepository(ctx, owner, repository, reqEditors...) +func (c *ClientWithResponses) DeleteRepositoryWithResponse(ctx context.Context, owner string, repository string, params *DeleteRepositoryParams, reqEditors ...RequestEditorFn) (*DeleteRepositoryResponse, error) { + rsp, err := c.DeleteRepository(ctx, owner, repository, params, reqEditors...) if err != nil { return nil, err } @@ -5877,7 +5904,7 @@ type ServerInterface interface { UploadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params UploadObjectParams) // delete repository // (DELETE /repos/{owner}/{repository}) - DeleteRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) + DeleteRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteRepositoryParams) // get repository // (GET /repos/{owner}/{repository}) GetRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) @@ -6026,7 +6053,7 @@ func (_ Unimplemented) UploadObject(ctx context.Context, w *JiaozifsResponse, r // delete repository // (DELETE /repos/{owner}/{repository}) -func (_ Unimplemented) DeleteRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) { +func (_ Unimplemented) DeleteRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteRepositoryParams) { w.WriteHeader(http.StatusNotImplemented) } @@ -6922,8 +6949,19 @@ func (siw *ServerInterfaceWrapper) DeleteRepository(w http.ResponseWriter, r *ht ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context + var params DeleteRepositoryParams + + // ------------- Optional query parameter "is_clean_data" ------------- + + err = runtime.BindQueryParameter("form", true, false, "is_clean_data", r.URL.Query(), ¶ms.IsCleanData) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "is_clean_data", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.DeleteRepository(r.Context(), &JiaozifsResponse{w}, r, owner, repository) + siw.Handler.DeleteRepository(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -8492,29 +8530,29 @@ var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 86194c76..bbda5c77 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -1607,6 +1607,11 @@ paths: - repo operationId: deleteRepository summary: delete repository + parameters: + - in: query + name: is_clean_data + schema: + type: boolean responses: 200: description: success to delete repository diff --git a/block/adapter.go b/block/adapter.go index f87d1e62..007e43a0 100644 --- a/block/adapter.go +++ b/block/adapter.go @@ -33,6 +33,7 @@ const ( BlockstoreTypeGS = "gs" BlockstoreTypeAzure = "azure" BlockstoreTypeLocal = "local" + BlockstoreIPFS = "ipfs" BlockstoreTypeMem = "mem" BlockstoreTypeTransient = "transient" ) diff --git a/block/factory/build.go b/block/factory/build.go index 8ef9b5a4..aedb4456 100644 --- a/block/factory/build.go +++ b/block/factory/build.go @@ -4,6 +4,8 @@ import ( "context" "fmt" + "github.com/jiaozifs/jiaozifs/block/ipfs" + "cloud.google.com/go/storage" "github.com/aws/aws-sdk-go-v2/service/s3" logging "github.com/ipfs/go-log/v2" @@ -39,6 +41,12 @@ func BuildBlockAdapter(ctx context.Context, c params.AdapterConfig) (block.Adapt return nil, err } return buildLocalAdapter(ctx, p) + case block.BlockstoreIPFS: + p, err := c.BlockstoreIpfsParams() + if err != nil { + return nil, err + } + return buildIpfsAdapter(ctx, p) case block.BlockstoreTypeS3: p, err := c.BlockstoreS3Params() if err != nil { @@ -67,6 +75,18 @@ func BuildBlockAdapter(ctx context.Context, c params.AdapterConfig) (block.Adapt } } +func buildIpfsAdapter(_ context.Context, params params.Ipfs) (*ipfs.Adapter, error) { + adapter, err := ipfs.NewAdapter(params.Url) + if err != nil { + return nil, fmt.Errorf("got error opening a local block adapter with path %s: %w", params.Url, err) + } + log.With( + "type", "local", + "url", params.Url, + ).Info("initialized blockstore adapter") + return adapter, nil +} + func buildLocalAdapter(_ context.Context, params params.Local) (*local.Adapter, error) { adapter, err := local.NewAdapter(params.Path, local.WithAllowedExternalPrefixes(params.AllowedExternalPrefixes), diff --git a/block/params/block.go b/block/params/block.go index 7a84e5e6..50b93406 100644 --- a/block/params/block.go +++ b/block/params/block.go @@ -10,11 +10,16 @@ type AdapterConfig interface { BlockstoreLocalParams() (Local, error) BlockstoreS3Params() (S3, error) BlockstoreGSParams() (GS, error) + BlockstoreIpfsParams() (Ipfs, error) BlockstoreAzureParams() (Azure, error) } type Mem struct{} +type Ipfs struct { + Url string +} + type Local struct { Path string ImportEnabled bool diff --git a/config/blockstore.go b/config/blockstore.go index 0dd677f9..6535b60f 100644 --- a/config/blockstore.go +++ b/config/blockstore.go @@ -18,6 +18,9 @@ type BlockStoreConfig struct { ImportHidden bool `mapstructure:"import_hidden" json:"import_hidden"` AllowedExternalPrefixes []string `mapstructure:"allowed_external_prefixes" json:"allowed_external_prefixes"` } `mapstructure:"local" json:"local"` + Ipfs *struct { + Url string `mapstructure:"url" json:"url"` + } `mapstructure:"ipfs" json:"ipfs"` S3 *struct { S3AuthInfo `mapstructure:",squash"` Region string `mapstructure:"region" json:"region"` @@ -62,6 +65,12 @@ func (c *BlockStoreConfig) BlockstoreType() string { return c.Type } +func (c *BlockStoreConfig) BlockstoreIpfsParams() (params.Ipfs, error) { + return params.Ipfs{ + Url: c.Ipfs.Url, + }, nil +} + func (c *BlockStoreConfig) BlockstoreS3Params() (params.S3, error) { var webIdentity *params.S3WebIdentity if c.S3.WebIdentity != nil { diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index b82875c9..b7019407 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -163,7 +163,7 @@ func (repositoryCtl RepositoryController) CreateRepository(ctx context.Context, w.Forbidden() return } - storageNamespace = cfg.DefaultNamespacePrefix + storageNamespace = utils.String(fmt.Sprintf("%s://%s", cfg.BlockstoreType(), repoID.String())) } else { storageNamespace = utils.String(fmt.Sprintf("%s://%s", repositoryCtl.PublicStorageConfig.BlockstoreType(), repoID.String())) } @@ -208,7 +208,7 @@ func (repositoryCtl RepositoryController) CreateRepository(ctx context.Context, w.JSON(repositoryToDto(createdRepo)) } -func (repositoryCtl RepositoryController) DeleteRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string) { +func (repositoryCtl RepositoryController) DeleteRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.DeleteRepositoryParams) { operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) @@ -287,6 +287,23 @@ func (repositoryCtl RepositoryController) DeleteRepository(ctx context.Context, w.Error(err) return } + } else if utils.BoolValue(params.IsCleanData) { + cfg := config.BlockStoreConfig{} + err = json.Unmarshal([]byte(utils.StringValue(repository.StorageAdapterParams)), &cfg) + if err != nil { + w.Error(err) + return + } + adapter, err := factory.BuildBlockAdapter(ctx, &cfg) + if err != nil { + w.Error(err) + return + } + err = adapter.RemoveNameSpace(ctx, *repository.StorageNamespace) + if err != nil { + w.Error(err) + return + } } w.OK() diff --git a/go.mod b/go.mod index e2d7d92f..2bd6c61e 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,10 @@ module github.com/jiaozifs/jiaozifs -go 1.20 +go 1.21 + +toolchain go1.22.0 + +replace github.com/ipfs/kubo v0.26.0 => github.com/hunjixin/kubo v0.4.22-0.20240222062558-6abb26a7771d require ( cloud.google.com/go/storage v1.33.0 @@ -27,20 +31,25 @@ require ( github.com/go-test/deep v1.1.0 github.com/golang-jwt/jwt/v5 v5.2.0 github.com/google/go-cmp v0.6.0 - github.com/google/uuid v1.4.0 + github.com/google/uuid v1.5.0 github.com/gorilla/sessions v1.2.2 github.com/hashicorp/go-version v1.6.0 github.com/hnlq715/golang-lru v0.4.0 + github.com/ipfs/boxo v0.18.0 github.com/ipfs/go-log/v2 v2.5.1 + github.com/ipfs/kubo v0.26.0 github.com/matoous/go-nanoid/v2 v2.0.0 github.com/minio/minio-go/v7 v7.0.64 github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/mapstructure v1.5.0 + github.com/modern-go/reflect2 v1.0.2 + github.com/multiformats/go-multiaddr v0.12.2 github.com/oapi-codegen/runtime v1.1.0 github.com/ory/dockertest/v3 v3.10.0 + github.com/pelletier/go-toml/v2 v2.1.0 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.17.0 + github.com/prometheus/client_golang v1.18.0 github.com/puzpuzpuz/xsync v1.5.2 github.com/rs/cors v1.10.1 github.com/smartystreets/goconvey v1.8.1 @@ -54,27 +63,28 @@ require ( github.com/uptrace/bun/driver/pgdriver v1.1.16 github.com/uptrace/bun/extra/bundebug v1.1.16 go.uber.org/fx v1.20.1 - go.uber.org/mock v0.3.0 + go.uber.org/mock v0.4.0 golang.org/x/crypto v0.18.0 - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 - golang.org/x/oauth2 v0.13.0 + golang.org/x/exp v0.0.0-20240119083558-1b970713d09a + golang.org/x/oauth2 v0.16.0 golang.org/x/text v0.14.0 - google.golang.org/api v0.147.0 + google.golang.org/api v0.149.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c gopkg.in/yaml.v2 v2.4.0 ) require ( - cloud.google.com/go v0.110.8 // indirect - cloud.google.com/go/compute v1.23.0 // indirect + cloud.google.com/go v0.111.0 // indirect + cloud.google.com/go/compute v1.23.3 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.2 // indirect + cloud.google.com/go/iam v1.1.5 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 // indirect + github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.3 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.8 // indirect @@ -90,99 +100,161 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.1 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.26.1 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/containerd/continuity v0.3.0 // indirect + github.com/crackcomm/go-gitignore v0.0.0-20231225121904-e25f5bc08668 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/docker/cli v23.0.6+incompatible // indirect github.com/docker/docker v23.0.6+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5 // indirect github.com/fatih/color v1.15.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/google/gopacket v1.1.19 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect - github.com/gorilla/mux v1.8.0 // indirect + github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/securecookie v1.1.2 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v0.3.15 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/yaml v0.2.0 // indirect + github.com/ipfs/bbloom v0.0.4 // indirect + github.com/ipfs/go-bitfield v1.1.0 // indirect + github.com/ipfs/go-block-format v0.2.0 // indirect + github.com/ipfs/go-cid v0.4.1 // indirect + github.com/ipfs/go-datastore v0.6.0 // indirect + github.com/ipfs/go-ds-measure v0.2.0 // indirect + github.com/ipfs/go-fs-lock v0.0.7 // indirect + github.com/ipfs/go-ipfs-cmds v0.10.0 // indirect + github.com/ipfs/go-ipfs-util v0.0.3 // indirect + github.com/ipfs/go-ipld-cbor v0.1.0 // indirect + github.com/ipfs/go-ipld-format v0.6.0 // indirect + github.com/ipfs/go-ipld-legacy v0.2.1 // indirect + github.com/ipfs/go-log v1.0.5 // indirect + github.com/ipfs/go-metrics-interface v0.0.1 // indirect + github.com/ipfs/go-unixfsnode v1.9.0 // indirect + github.com/ipld/go-car/v2 v2.13.1 // indirect + github.com/ipld/go-codec-dagpb v1.6.0 // indirect + github.com/ipld/go-ipld-prime v0.21.0 // indirect + github.com/jbenet/goprocess v0.1.4 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/jtolds/gls v4.20.0+incompatible // indirect - github.com/klauspost/compress v1.17.0 // indirect - github.com/klauspost/cpuid/v2 v2.2.5 // indirect + github.com/klauspost/compress v1.17.4 // indirect + github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/lib/pq v1.10.9 // indirect + github.com/libp2p/go-buffer-pool v0.1.0 // indirect + github.com/libp2p/go-cidranger v1.1.0 // indirect + github.com/libp2p/go-libp2p v0.32.2 // indirect + github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect + github.com/libp2p/go-libp2p-kad-dht v0.24.4 // indirect + github.com/libp2p/go-libp2p-kbucket v0.6.3 // indirect + github.com/libp2p/go-libp2p-record v0.2.0 // indirect + github.com/libp2p/go-libp2p-routing-helpers v0.7.3 // indirect + github.com/libp2p/go-msgio v0.3.0 // indirect + github.com/libp2p/go-netroute v0.2.1 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/miekg/dns v1.1.58 // indirect github.com/minio/md5-simd v1.1.2 // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/mr-tron/base58 v1.2.0 // indirect + github.com/multiformats/go-base32 v0.1.0 // indirect + github.com/multiformats/go-base36 v0.2.0 // indirect + github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect + github.com/multiformats/go-multibase v0.2.0 // indirect + github.com/multiformats/go-multicodec v0.9.0 // indirect + github.com/multiformats/go-multihash v0.2.3 // indirect + github.com/multiformats/go-multistream v0.5.0 // indirect + github.com/multiformats/go-varint v0.0.7 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect github.com/opencontainers/runc v1.1.7 // indirect - github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/perimeterx/marshmallow v1.1.4 // indirect + github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect - github.com/prometheus/common v0.44.0 // indirect - github.com/prometheus/procfs v0.11.1 // indirect + github.com/polydawn/refmt v0.89.0 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.46.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/rs/xid v1.5.0 // indirect github.com/sagikazarmark/locafero v0.3.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/samber/lo v1.39.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/smarty/assertions v1.15.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/afero v1.10.0 // indirect github.com/spf13/cast v1.5.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc // indirect + github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 // indirect + github.com/whyrusleeping/cbor-gen v0.0.0-20240109153615-66e95c3e8a87 // indirect + github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect go.opencensus.io v0.24.0 // indirect - go.uber.org/atomic v1.11.0 // indirect - go.uber.org/dig v1.17.0 // indirect - go.uber.org/multierr v1.9.0 // indirect - go.uber.org/zap v1.23.0 // indirect + go.opentelemetry.io/otel v1.22.0 // indirect + go.opentelemetry.io/otel/metric v1.22.0 // indirect + go.opentelemetry.io/otel/trace v1.22.0 // indirect + go.uber.org/dig v1.17.1 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.26.0 // indirect + go4.org v0.0.0-20230225012048-214862532bf5 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.20.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.16.0 // indirect golang.org/x/tools v0.17.0 // indirect - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.31.0 // indirect + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect + gonum.org/v1/gonum v0.14.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect + google.golang.org/grpc v1.60.1 // indirect + google.golang.org/protobuf v1.32.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + lukechampine.com/blake3 v1.2.1 // indirect mellium.im/sasl v0.3.1 // indirect ) diff --git a/go.sum b/go.sum index ada658f3..9872177b 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +bazil.org/fuse v0.0.0-20200117225306-7b5117fecadc h1:utDghgcjE8u+EBjHOgYT+dJPcnDF05KqWMBcjuJy510= +bazil.org/fuse v0.0.0-20200117225306-7b5117fecadc/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -17,22 +19,22 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.110.8 h1:tyNdfIxjzaWctIiLYOTalaLKZ17SI44SKFW26QbOhME= -cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk= +cloud.google.com/go v0.111.0 h1:YHLKNupSD1KqjDbQ3+LVdQ81h/UJbJyZG203cEfnQgM= +cloud.google.com/go v0.111.0/go.mod h1:0mibmpKP1TyOOFYQY5izo0LnT+ecvOQ0Sg3OdmMiNRU= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= -cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/iam v1.1.2 h1:gacbrBdWcoVmGLozRuStX45YKvJtzIjJdAolzUs1sm4= -cloud.google.com/go/iam v1.1.2/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= +cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= +cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -46,6 +48,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f cloud.google.com/go/storage v1.33.0 h1:PVrDOkIC8qQVa1P3SXGpQvfuJhN2LHOoyZvWs8D2X5M= cloud.google.com/go/storage v1.33.0/go.mod h1:Hhh/dogNRGca7IWv1RC2YqEn0c0G77ctA/OxflYkiD8= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0 h1:fb8kj/Dh4CSwgsOzHeZY4Xh68cFVbzXx+ONXGMY//4w= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0/go.mod h1:uReU2sSxZExRPBAg3qKzmAucSi51+SP1OhohieR821Q= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 h1:BMAjVKJM0U/CYF27gA0ZMmXGkOcvfFtD0oHVZ1TIPRI= @@ -53,6 +57,7 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0/go.mod h1:1fXstnBMas5kzG github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0 h1:d81/ng9rET2YqdVkVwkb6EXeRrLJIwyGnJcAlAWKwhs= github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0 h1:Ma67P/GGprNwsslzEH6+Kb8nybI8jpDTm4Wmzu2ReK8= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0/go.mod h1:c+Lifp3EDEamAkPVzMooRNOK6CZjNSdEnf1A7jsI9u4= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0 h1:gggzg0SUMs6SQbEw+3LoSsYf9YMjkupeAnHMX8O9mmY= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0/go.mod h1:+6KLcKIVgxoBDMqMO/Nvy7bZ9a0nbU3I1DtFQK3YvB4= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= @@ -61,6 +66,8 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 h1:WpB/QDNLpMw github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Jorropo/jsync v1.0.1 h1:6HgRolFZnsdfzRUj+ImB9og1JYOxQoReSywkHOGSaUU= +github.com/Jorropo/jsync v1.0.1/go.mod h1:jCOZj3vrBCri3bSU3ErUYvevKlnbssrXeCivybS5ABQ= github.com/MadAppGang/httplog v1.3.0 h1:1XU54TO8kiqTeO+7oZLKAM3RP/cJ7SadzslRcKspVHo= github.com/MadAppGang/httplog v1.3.0/go.mod h1:gpYEdkjh/Cda6YxtDy4AB7KY+fR7mb3SqBZw74A5hJ4= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= @@ -70,6 +77,10 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8 github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 h1:ZBbLwSJqkHBuFDA6DUhhse0IGJ7T5bemHyNILUjvOq4= github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2/go.mod h1:VSw57q4QFiWDbRnjdX8Cb3Ow0SFncRw+bA/ofY6Q83w= +github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 h1:ez/4by2iGztzR4L0zgAOR8lTQK9VlyBVVd7G4omaOQs= +github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5 h1:iW0a5ljuFxkLGPNem5Ui+KBjFJzKg4Fv2fnxe4dvzpM= +github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5/go.mod h1:Y2QMoi1vgtOIfc+6DhrMOGkLoGzqSV2rKp4Sm+opsyA= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/aws/aws-sdk-go-v2 v1.23.4 h1:2P20ZjH0ouSAu/6yZep8oCmTReathLuEu6dwoqEgjts= @@ -111,17 +122,24 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.26.1/go.mod h1:YtXUl/sfnS06VksYhr855 github.com/aws/smithy-go v1.18.1 h1:pOdBTUfXNazOlxLrgeYalVnuTpKreACHtc62xLwIB3c= github.com/aws/smithy-go v1.18.1/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benburkert/dns v0.0.0-20190225204957-d356cf78cdfc h1:eyDlmf21vuKN61WoxV2cQLDH/PBDyyjIhUI4kT2o1yM= github.com/benburkert/dns v0.0.0-20190225204957-d356cf78cdfc/go.mod h1:6ul4nJKqsreAIBK5lUkibcUn2YBU6CvDzlKDH+dtZsQ= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/brianvoe/gofakeit/v6 v6.25.0 h1:ZpFjktOpLZUeF8q223o0rUuXtA+m5qW5srjvVi+JkXk= github.com/brianvoe/gofakeit/v6 v6.25.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/ceramicnetwork/go-dag-jose v0.1.0 h1:yJ/HVlfKpnD3LdYP03AHyTvbm3BpPiz2oZiOeReJRdU= +github.com/ceramicnetwork/go-dag-jose v0.1.0/go.mod h1:qYA1nYt0X8u4XoMAVoOV3upUVKtrxy/I670Dg5F0wjI= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -131,18 +149,39 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= +github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/crackcomm/go-gitignore v0.0.0-20231225121904-e25f5bc08668 h1:ZFUue+PNxmHlu7pYv+IYMtqlaO/0VwaGEqKepZf9JpA= +github.com/crackcomm/go-gitignore v0.0.0-20231225121904-e25f5bc08668/go.mod h1:p1d6YEZWvFzEh4KLyvBcVSnrfNDDvK2zfK/4x2v/4pE= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/cskr/pubsub v1.0.2 h1:vlOzMhl6PFn60gRlTQQsIfVwaPB/B/8MziK8FhEPt/0= +github.com/cskr/pubsub v1.0.2/go.mod h1:/8MzYXk/NJAz782G8RPkFzXTZVu63VotefPnR9TIRis= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= +github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/deepmap/oapi-codegen/v2 v2.0.1-0.20231120160225-add3126ee845 h1:QlRkcr1t+VcHkdk8WJDhuiI94RqmjSgtflB3Q+H8X2k= github.com/deepmap/oapi-codegen/v2 v2.0.1-0.20231120160225-add3126ee845/go.mod h1:pB9cROTwrn6Gj3Rtmcmp5fwV23znquC9tY1rR6+/R3s= +github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8= +github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= +github.com/dgraph-io/ristretto v0.0.2 h1:a5WaUrDa0qm0YrAAS1tUykT5El3kt62KNZZeMxQn3po= +github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docker/cli v23.0.6+incompatible h1:CScadyCJ2ZKUDpAMZta6vK8I+6/m60VIjGIV7Wg/Eu4= github.com/docker/cli v23.0.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/docker v23.0.6+incompatible h1:aBD4np894vatVX99UTx/GyOUOK4uEcROwA3+bQhEcoU= @@ -153,6 +192,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/4= +github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -161,15 +202,24 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5 h1:BBso6MBKW8ncyZLv37o+KNyy0HrrHgfnOaGQC2qvN+A= +github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5/go.mod h1:JpoxHjuQauoxiFMl1ie8Xc/7TfLuMZ5eOCONd1sUBHg= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/fergusstrange/embedded-postgres v1.25.0 h1:sa+k2Ycrtz40eCRPOzI7Ry7TtkWXXJ+YRsxpKMDhxK0= github.com/fergusstrange/embedded-postgres v1.25.0/go.mod h1:t/MLs0h9ukYM6FSt99R7InCHs1nW0ordoVCcnzmpTYw= github.com/flowchartsman/swaggerui v0.0.0-20221017034628-909ed4f3701b h1:oy54yVy300Db264NfQCJubZHpJOl+SoT6udALQdFbSI= github.com/flowchartsman/swaggerui v0.0.0-20221017034628-909ed4f3701b/go.mod h1:/RJwPD5L4xWgCbqQ1L5cB12ndgfKKT54n9cZFf+8pus= -github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/flynn/noise v1.0.1 h1:vPp/jdQLXC6ppsXSj/pM3W1BIJ5FEHE2TulSJBpb43Y= +github.com/flynn/noise v1.0.1/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= +github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= +github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/getkin/kin-openapi v0.118.0 h1:z43njxPmJ7TaPpMSCQb7PN0dEYno4tyBPQcrFdHoLuM= github.com/getkin/kin-openapi v0.118.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= @@ -177,6 +227,11 @@ github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNIT github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= @@ -186,9 +241,14 @@ github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogB github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= @@ -221,8 +281,11 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -240,11 +303,15 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -255,38 +322,61 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42 h1:dHLYa5D8/Ta0aLR2XcPsrkpAgGeFs6thhMcQK0oQ0n8= +github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.1 h1:SBWmZhjUDRorQxrN0nwzf+AHBxnbFjViHQS4P0yVpmQ= -github.com/googleapis/enterprise-certificate-proxy v0.3.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hnlq715/golang-lru v0.4.0 h1:gyo/wIvLE6Upf1wucAfwTjpR+BQ5Lli2766H2MnNPv0= github.com/hnlq715/golang-lru v0.4.0/go.mod h1:RBkgDAtlu0SgTPvpb4VW2/RQnkCBMRD3Lr6B9RhsAS8= github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8= +github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI= +github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= +github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= +github.com/hunjixin/kubo v0.4.22-0.20240222062558-6abb26a7771d h1:jkqM5O8Be8BDPHpPK/MsjmEfziZpUOFvgyFlf95lxj4= +github.com/hunjixin/kubo v0.4.22-0.20240222062558-6abb26a7771d/go.mod h1:igcUNmVhnoLbxNb4MFcpXkiJIjkPxjCdUlqKbzrsCIE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= @@ -296,8 +386,105 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= +github.com/ipfs-shipyard/nopfs v0.0.12 h1:mvwaoefDF5VI9jyvgWCmaoTJIJFAfrbyQV5fJz35hlk= +github.com/ipfs-shipyard/nopfs v0.0.12/go.mod h1:mQyd0BElYI2gB/kq/Oue97obP4B3os4eBmgfPZ+hnrE= +github.com/ipfs-shipyard/nopfs/ipfs v0.13.2-0.20231027223058-cde3b5ba964c h1:7UynTbtdlt+w08ggb1UGLGaGjp1mMaZhoTZSctpn5Ak= +github.com/ipfs-shipyard/nopfs/ipfs v0.13.2-0.20231027223058-cde3b5ba964c/go.mod h1:6EekK/jo+TynwSE/ZOiOJd4eEvRXoavEC3vquKtv4yI= +github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= +github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= +github.com/ipfs/boxo v0.18.0 h1:MOL9/AgoV3e7jlVMInicaSdbgralfqSsbkc31dZ9tmw= +github.com/ipfs/boxo v0.18.0/go.mod h1:pIZgTWdm3k3pLF9Uq6MB8JEcW07UDwNJjlXW1HELW80= +github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA= +github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU= +github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNiW1Ycs= +github.com/ipfs/go-block-format v0.2.0/go.mod h1:+jpL11nFx5A/SPpsoBn6Bzkra/zaArfSmsknbPMYgzM= +github.com/ipfs/go-blockservice v0.5.0 h1:B2mwhhhVQl2ntW2EIpaWPwSCxSuqr5fFA93Ms4bYLEY= +github.com/ipfs/go-blockservice v0.5.0/go.mod h1:W6brZ5k20AehbmERplmERn8o2Ni3ZZubvAxaIUeaT6w= +github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= +github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= +github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= +github.com/ipfs/go-cidutil v0.1.0 h1:RW5hO7Vcf16dplUU60Hs0AKDkQAVPVplr7lk97CFL+Q= +github.com/ipfs/go-cidutil v0.1.0/go.mod h1:e7OEVBMIv9JaOxt9zaGEmAoSlXW9jdFZ5lP/0PwcfpA= +github.com/ipfs/go-datastore v0.5.0/go.mod h1:9zhEApYMTl17C8YDp7JmU7sQZi2/wqiYh73hakZ90Bk= +github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk= +github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8= +github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= +github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= +github.com/ipfs/go-ds-badger v0.3.0 h1:xREL3V0EH9S219kFFueOYJJTcjgNSZ2HY1iSvN7U1Ro= +github.com/ipfs/go-ds-badger v0.3.0/go.mod h1:1ke6mXNqeV8K3y5Ak2bAA0osoTfmxUdupVCGm4QUIek= +github.com/ipfs/go-ds-flatfs v0.5.1 h1:ZCIO/kQOS/PSh3vcF1H6a8fkRGS7pOfwfPdx4n/KJH4= +github.com/ipfs/go-ds-flatfs v0.5.1/go.mod h1:RWTV7oZD/yZYBKdbVIFXTX2fdY2Tbvl94NsWqmoyAX4= +github.com/ipfs/go-ds-leveldb v0.5.0 h1:s++MEBbD3ZKc9/8/njrn4flZLnCuY9I79v94gBUNumo= +github.com/ipfs/go-ds-leveldb v0.5.0/go.mod h1:d3XG9RUDzQ6V4SHi8+Xgj9j1XuEk1z82lquxrVbml/Q= +github.com/ipfs/go-ds-measure v0.2.0 h1:sG4goQe0KDTccHMyT45CY1XyUbxe5VwTKpg2LjApYyQ= +github.com/ipfs/go-ds-measure v0.2.0/go.mod h1:SEUD/rE2PwRa4IQEC5FuNAmjJCyYObZr9UvVh8V3JxE= +github.com/ipfs/go-fs-lock v0.0.7 h1:6BR3dajORFrFTkb5EpCUFIAypsoxpGpDSVUdFwzgL9U= +github.com/ipfs/go-fs-lock v0.0.7/go.mod h1:Js8ka+FNYmgQRLrRXzU3CB/+Csr1BwrRilEcvYrHhhc= +github.com/ipfs/go-ipfs-blockstore v1.3.0 h1:m2EXaWgwTzAfsmt5UdJ7Is6l4gJcaM/A12XwJyvYvMM= +github.com/ipfs/go-ipfs-blockstore v1.3.0/go.mod h1:KgtZyc9fq+P2xJUiCAzbRdhhqJHvsw8u2Dlqy2MyRTE= +github.com/ipfs/go-ipfs-blocksutil v0.0.1 h1:Eh/H4pc1hsvhzsQoMEP3Bke/aW5P5rVM1IWFJMcGIPQ= +github.com/ipfs/go-ipfs-blocksutil v0.0.1/go.mod h1:Yq4M86uIOmxmGPUHv/uI7uKqZNtLb449gwKqXjIsnRk= +github.com/ipfs/go-ipfs-chunker v0.0.5 h1:ojCf7HV/m+uS2vhUGWcogIIxiO5ubl5O57Q7NapWLY8= +github.com/ipfs/go-ipfs-chunker v0.0.5/go.mod h1:jhgdF8vxRHycr00k13FM8Y0E+6BoalYeobXmUyTreP8= +github.com/ipfs/go-ipfs-cmds v0.10.0 h1:ZB4+RgYaH4UARfJY0uLKl5UXgApqnRjKbuCiJVcErYk= +github.com/ipfs/go-ipfs-cmds v0.10.0/go.mod h1:sX5d7jkCft9XLPnkgEfXY0z2UBOB5g6fh/obBS0enJE= +github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= +github.com/ipfs/go-ipfs-delay v0.0.1 h1:r/UXYyRcddO6thwOnhiznIAiSvxMECGgtv35Xs1IeRQ= +github.com/ipfs/go-ipfs-delay v0.0.1/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= +github.com/ipfs/go-ipfs-ds-help v1.1.0 h1:yLE2w9RAsl31LtfMt91tRZcrx+e61O5mDxFRR994w4Q= +github.com/ipfs/go-ipfs-ds-help v1.1.0/go.mod h1:YR5+6EaebOhfcqVCyqemItCLthrpVNot+rsOU/5IatU= +github.com/ipfs/go-ipfs-exchange-interface v0.2.0 h1:8lMSJmKogZYNo2jjhUs0izT+dck05pqUw4mWNW9Pw6Y= +github.com/ipfs/go-ipfs-exchange-interface v0.2.0/go.mod h1:z6+RhJuDQbqKguVyslSOuVDhqF9JtTrO3eptSAiW2/Y= +github.com/ipfs/go-ipfs-exchange-offline v0.3.0 h1:c/Dg8GDPzixGd0MC8Jh6mjOwU57uYokgWRFidfvEkuA= +github.com/ipfs/go-ipfs-exchange-offline v0.3.0/go.mod h1:MOdJ9DChbb5u37M1IcbrRB02e++Z7521fMxqCNRrz9s= +github.com/ipfs/go-ipfs-pq v0.0.3 h1:YpoHVJB+jzK15mr/xsWC574tyDLkezVrDNeaalQBsTE= +github.com/ipfs/go-ipfs-pq v0.0.3/go.mod h1:btNw5hsHBpRcSSgZtiNm/SLj5gYIZ18AKtv3kERkRb4= +github.com/ipfs/go-ipfs-redirects-file v0.1.1 h1:Io++k0Vf/wK+tfnhEh63Yte1oQK5VGT2hIEYpD0Rzx8= +github.com/ipfs/go-ipfs-redirects-file v0.1.1/go.mod h1:tAwRjCV0RjLTjH8DR/AU7VYvfQECg+lpUy2Mdzv7gyk= +github.com/ipfs/go-ipfs-util v0.0.2/go.mod h1:CbPtkWJzjLdEcezDns2XYaehFVNXG9zrdrtMecczcsQ= +github.com/ipfs/go-ipfs-util v0.0.3 h1:2RFdGez6bu2ZlZdI+rWfIdbQb1KudQp3VGwPtdNCmE0= +github.com/ipfs/go-ipfs-util v0.0.3/go.mod h1:LHzG1a0Ig4G+iZ26UUOMjHd+lfM84LZCrn17xAKWBvs= +github.com/ipfs/go-ipld-cbor v0.1.0 h1:dx0nS0kILVivGhfWuB6dUpMa/LAwElHPw1yOGYopoYs= +github.com/ipfs/go-ipld-cbor v0.1.0/go.mod h1:U2aYlmVrJr2wsUBU67K4KgepApSZddGRDWBYR0H4sCk= +github.com/ipfs/go-ipld-format v0.6.0 h1:VEJlA2kQ3LqFSIm5Vu6eIlSxD/Ze90xtc4Meten1F5U= +github.com/ipfs/go-ipld-format v0.6.0/go.mod h1:g4QVMTn3marU3qXchwjpKPKgJv+zF+OlaKMyhJ4LHPg= +github.com/ipfs/go-ipld-git v0.1.1 h1:TWGnZjS0htmEmlMFEkA3ogrNCqWjIxwr16x1OsdhG+Y= +github.com/ipfs/go-ipld-git v0.1.1/go.mod h1:+VyMqF5lMcJh4rwEppV0e6g4nCCHXThLYYDpKUkJubI= +github.com/ipfs/go-ipld-legacy v0.2.1 h1:mDFtrBpmU7b//LzLSypVrXsD8QxkEWxu5qVxN99/+tk= +github.com/ipfs/go-ipld-legacy v0.2.1/go.mod h1:782MOUghNzMO2DER0FlBR94mllfdCJCkTtDtPM51otM= +github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8= +github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo= +github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= +github.com/ipfs/go-log/v2 v2.3.0/go.mod h1:QqGoj30OTpnKaG/LKTGTxoP2mmQtjVMEnK72gynbe/g= github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= +github.com/ipfs/go-merkledag v0.11.0 h1:DgzwK5hprESOzS4O1t/wi6JDpyVQdvm9Bs59N/jqfBY= +github.com/ipfs/go-merkledag v0.11.0/go.mod h1:Q4f/1ezvBiJV0YCIXvt51W/9/kqJGH4I1LsA7+djsM4= +github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fGD6n0jO4kdg= +github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY= +github.com/ipfs/go-peertaskqueue v0.8.1 h1:YhxAs1+wxb5jk7RvS0LHdyiILpNmRIRnZVztekOF0pg= +github.com/ipfs/go-peertaskqueue v0.8.1/go.mod h1:Oxxd3eaK279FxeydSPPVGHzbwVeHjatZ2GA8XD+KbPU= +github.com/ipfs/go-unixfs v0.4.5 h1:wj8JhxvV1G6CD7swACwSKYa+NgtdWC1RUit+gFnymDU= +github.com/ipfs/go-unixfs v0.4.5/go.mod h1:BIznJNvt/gEx/ooRMI4Us9K8+qeGO7vx1ohnbk8gjFg= +github.com/ipfs/go-unixfsnode v1.9.0 h1:ubEhQhr22sPAKO2DNsyVBW7YB/zA8Zkif25aBvz8rc8= +github.com/ipfs/go-unixfsnode v1.9.0/go.mod h1:HxRu9HYHOjK6HUqFBAi++7DVoWAHn0o4v/nZ/VA+0g8= +github.com/ipfs/go-verifcid v0.0.2 h1:XPnUv0XmdH+ZIhLGKg6U2vaPaRDXb9urMyNVCE7uvTs= +github.com/ipfs/go-verifcid v0.0.2/go.mod h1:40cD9x1y4OWnFXbLNJYRe7MpNvWlMn3LZAG5Wb4xnPU= +github.com/ipld/go-car/v2 v2.13.1 h1:KnlrKvEPEzr5IZHKTXLAEub+tPrzeAFQVRlSQvuxBO4= +github.com/ipld/go-car/v2 v2.13.1/go.mod h1:QkdjjFNGit2GIkpQ953KBwowuoukoM75nP/JI1iDJdo= +github.com/ipld/go-codec-dagpb v1.6.0 h1:9nYazfyu9B1p3NAgfVdpRco3Fs2nFC72DqVsMj6rOcc= +github.com/ipld/go-codec-dagpb v1.6.0/go.mod h1:ANzFhfP2uMJxRBr8CE+WQWs5UsNa0pYtmKZ+agnUw9s= +github.com/ipld/go-ipld-prime v0.21.0 h1:n4JmcpOlPDIxBcY037SVfpd1G+Sj1nKZah0m6QH9C2E= +github.com/ipld/go-ipld-prime v0.21.0/go.mod h1:3RLqy//ERg/y5oShXXdx5YIp50cFGOanyMctpPjsvxQ= +github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20230102063945-1a409dc236dd h1:gMlw/MhNr2Wtp5RwGdsW23cs+yCuj9k2ON7i9MiJlRo= +github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20230102063945-1a409dc236dd/go.mod h1:wZ8hH8UxeryOs4kJEJaiui/s00hDSbE37OKsL47g+Sw= +github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= +github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= +github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk= +github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= +github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= +github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= @@ -315,13 +502,16 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= -github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= +github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0= +github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -333,27 +523,77 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= +github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= +github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c= +github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= +github.com/libp2p/go-doh-resolver v0.4.0 h1:gUBa1f1XsPwtpE1du0O+nnZCUqtG7oYi7Bb+0S7FQqw= +github.com/libp2p/go-doh-resolver v0.4.0/go.mod h1:v1/jwsFusgsWIGX/c6vCRrnJ60x7bhTiq/fs2qt0cAg= +github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM= +github.com/libp2p/go-flow-metrics v0.1.0/go.mod h1:4Xi8MX8wj5aWNDAZttg6UPmc0ZrnFNsMtpsYUClFtro= +github.com/libp2p/go-libp2p v0.32.2 h1:s8GYN4YJzgUoyeYNPdW7JZeZ5Ee31iNaIBfGYMAY4FQ= +github.com/libp2p/go-libp2p v0.32.2/go.mod h1:E0LKe+diV/ZVJVnOJby8VC5xzHF0660osg71skcxJvk= +github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94= +github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8= +github.com/libp2p/go-libp2p-kad-dht v0.24.4 h1:ktNiJe7ffsJ1wX3ULpMCwXts99mPqGFSE/Qn1i8pErQ= +github.com/libp2p/go-libp2p-kad-dht v0.24.4/go.mod h1:ybWBJ5Fbvz9sSLkNtXt+2+bK0JB8+tRPvhBbRGHegRU= +github.com/libp2p/go-libp2p-kbucket v0.6.3 h1:p507271wWzpy2f1XxPzCQG9NiN6R6lHL9GiSErbQQo0= +github.com/libp2p/go-libp2p-kbucket v0.6.3/go.mod h1:RCseT7AH6eJWxxk2ol03xtP9pEHetYSPXOaJnOiD8i0= +github.com/libp2p/go-libp2p-pubsub v0.10.0 h1:wS0S5FlISavMaAbxyQn3dxMOe2eegMfswM471RuHJwA= +github.com/libp2p/go-libp2p-pubsub v0.10.0/go.mod h1:1OxbaT/pFRO5h+Dpze8hdHQ63R0ke55XTs6b6NwLLkw= +github.com/libp2p/go-libp2p-pubsub-router v0.6.0 h1:D30iKdlqDt5ZmLEYhHELCMRj8b4sFAqrUcshIUvVP/s= +github.com/libp2p/go-libp2p-pubsub-router v0.6.0/go.mod h1:FY/q0/RBTKsLA7l4vqC2cbRbOvyDotg8PJQ7j8FDudE= +github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0= +github.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk= +github.com/libp2p/go-libp2p-routing-helpers v0.7.3 h1:u1LGzAMVRK9Nqq5aYDVOiq/HaB93U9WWczBzGyAC5ZY= +github.com/libp2p/go-libp2p-routing-helpers v0.7.3/go.mod h1:cN4mJAD/7zfPKXBcs9ze31JGYAZgzdABEm+q/hkswb8= +github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= +github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg= +github.com/libp2p/go-libp2p-xor v0.1.0 h1:hhQwT4uGrBcuAkUGXADuPltalOdpf9aag9kaYNT2tLA= +github.com/libp2p/go-libp2p-xor v0.1.0/go.mod h1:LSTM5yRnjGZbWNTA/hRwq2gGFrvRIbQJscoIL/u6InY= +github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0= +github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM= +github.com/libp2p/go-nat v0.2.0 h1:Tyz+bUFAYqGyJ/ppPPymMGbIgNRH+WqC5QrT5fKrrGk= +github.com/libp2p/go-nat v0.2.0/go.mod h1:3MJr+GRpRkyT65EpVPBstXLvOlAPzUVlG6Pwg9ohLJk= +github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU= +github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ= +github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s= +github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= +github.com/libp2p/go-yamux/v4 v4.0.1 h1:FfDR4S1wj6Bw2Pqbc8Uz7pCxeRBPbwsBbEdfwiCypkQ= +github.com/libp2p/go-yamux/v4 v4.0.1/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4= +github.com/libp2p/zeroconf/v2 v2.2.0 h1:Cup06Jv6u81HLhIj1KasuNM/RHHrJ8T7wOTS4+Tv53Q= +github.com/libp2p/zeroconf/v2 v2.2.0/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0DmyYyNO1Xs= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= +github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U= github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL+9Tj0= github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= +github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= +github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc= +github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b/go.mod h1:lxPUiZwKoFL8DUUmalo2yJJUCxbPKtm8OKfqr2/FTNU= +github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc h1:PTfri+PuQmWDqERdnNMiD9ZejrlswWrCpBEZgWOiTrc= +github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s= +github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/minio-go/v7 v7.0.64 h1:Zdza8HwOzkld0ZG/og50w56fKi6AAyfqfifmasD9n2Q= github.com/minio/minio-go/v7 v7.0.64/go.mod h1:R4WVUR6ZTedlCcGwZRauLMIKjgyaWxhs4Mqi/OMPmEc= +github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -369,22 +609,99 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= +github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= +github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= +github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= +github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= +github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= +github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= +github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= +github.com/multiformats/go-multiaddr v0.12.2 h1:9G9sTY/wCYajKa9lyfWPmpZAwe6oV+Wb1zcmMS1HG24= +github.com/multiformats/go-multiaddr v0.12.2/go.mod h1:GKyaTYjZRdcUhyOetrxTk9z0cW+jA/YrnqTOvKgi44M= +github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A= +github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk= +github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= +github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= +github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= +github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= +github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= +github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg= +github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= +github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= +github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= +github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= +github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= +github.com/multiformats/go-multistream v0.5.0 h1:5htLSLl7lvJk3xx3qT/8Zm9J4K8vEOf/QGkvOGQAyiE= +github.com/multiformats/go-multistream v0.5.0/go.mod h1:n6tMZiwiP2wUsR8DgfDWw1dydlEqV3l6N3/GBsX6ILA= +github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= +github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/oapi-codegen/runtime v1.1.0 h1:rJpoNUawn5XTvekgfkvSZr0RqEnoYpFkyvrzfWeFKWM= github.com/oapi-codegen/runtime v1.1.0/go.mod h1:BeSfBkWWWnAnGdyS+S/GnlbmHKzf8/hwkvelJZDeKA8= +github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs= +github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v1.1.7 h1:y2EZDS8sNng4Ksf0GUYNhKbTShZJPJg1FiXJNH/uoCk= github.com/opencontainers/runc v1.1.7/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50= +github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg= +github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/openzipkin/zipkin-go v0.4.2 h1:zjqfqHjUpPmB3c1GlCvvgsM1G4LkvqQbBDueDOCg/jA= +github.com/openzipkin/zipkin-go v0.4.2/go.mod h1:ZeVkFjuuBiSy13y8vpSDCjMi9GoI3hPpCJSBx/EYFhY= github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55FuFkxqLw= github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 h1:1/WtZae0yGtPq+TI6+Tv1WTxkukpXeMlviSxvL7SRgk= +github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9/go.mod h1:x3N5drFsm2uilKKuuYo6LdyD8vZAW55sH/9w+pbo1sw= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= +github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8= +github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0= +github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= +github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= +github.com/pion/ice/v2 v2.3.6 h1:Jgqw36cAud47iD+N6rNX225uHvrgWtAlHfVyOQc3Heg= +github.com/pion/ice/v2 v2.3.6/go.mod h1:9/TzKDRwBVAPsC+YOrKH/e3xDrubeTRACU9/sHQarsU= +github.com/pion/interceptor v0.1.17 h1:prJtgwFh/gB8zMqGZoOgJPHivOwVAp61i2aG61Du/1w= +github.com/pion/interceptor v0.1.17/go.mod h1:SY8kpmfVBvrbUzvj2bsXz7OJt5JvmVNZ+4Kjq7FcwrI= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/mdns v0.0.7 h1:P0UB4Sr6xDWEox0kTVxF0LmQihtCbSAdW0H2nEgkA3U= +github.com/pion/mdns v0.0.7/go.mod h1:4iP2UbeFhLI/vWju/bw6ZfwjJzk0z8DNValjGxR/dD8= +github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= +github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= +github.com/pion/rtcp v1.2.10 h1:nkr3uj+8Sp97zyItdN60tE/S6vk4al5CPRR6Gejsdjc= +github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I= +github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA= +github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= +github.com/pion/sctp v1.8.7 h1:JnABvFakZueGAn4KU/4PSKg+GWbF6QWbKTWZOSGJjXw= +github.com/pion/sctp v1.8.7/go.mod h1:g1Ul+ARqZq5JEmoFy87Q/4CePtKnTJ1QCL9dBBdN6AU= +github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw= +github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw= +github.com/pion/srtp/v2 v2.0.15 h1:+tqRtXGsGwHC0G0IUIAzRmdkHvriF79IHVfZGfHrQoA= +github.com/pion/srtp/v2 v2.0.15/go.mod h1:b/pQOlDrbB0HEH5EUAQXzSYxikFbNcNuKmF8tM0hCtw= +github.com/pion/stun v0.6.0 h1:JHT/2iyGDPrFWE8NNC15wnddBN8KifsEDw8swQmrEmU= +github.com/pion/stun v0.6.0/go.mod h1:HPqcfoeqQn9cuaet7AOmB5e5xkObu9DwBdurwLKO9oA= +github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c= +github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= +github.com/pion/turn/v2 v2.1.0 h1:5wGHSgGhJhP/RpabkUb/T9PdsAjkGLS6toYz5HNzoSI= +github.com/pion/turn/v2 v2.1.0/go.mod h1:yrT5XbXSGX1VFSF31A3c1kCNB5bBZgk/uu5LET162qs= +github.com/pion/webrtc/v3 v3.2.9 h1:U8NSjQDlZZ+Iy/hg42Q/u6mhEVSXYvKrOIZiZwYTfLc= +github.com/pion/webrtc/v3 v3.2.9/go.mod h1:gjQLMZeyN3jXBGdxGmUYCyKjOuYX/c99BDjGqmadq0A= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -395,17 +712,29 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= -github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4= +github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= +github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM= -github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= -github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= -github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= -github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= -github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y= +github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/puzpuzpuz/xsync v1.5.2 h1:yRAP4wqSOZG+/4pxJ08fPTwrfL0IzE/LKQ/cw509qGY= github.com/puzpuzpuz/xsync v1.5.2/go.mod h1:K98BYhX3k1dQ2M63t1YNVDanbwUPmBCAhNmVrrxfiGg= +github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= +github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= +github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/quic-go/quic-go v0.40.1 h1:X3AGzUNFs0jVuO3esAGnTfvdgvL4fq655WaOi1snv1Q= +github.com/quic-go/quic-go v0.40.1/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c= +github.com/quic-go/webtransport-go v0.6.0 h1:CvNsKqc4W2HljHJnoT+rMmbRJybShZ0YPFDD3NxaZLY= +github.com/quic-go/webtransport-go v0.6.0/go.mod h1:9KjU4AEBqEQidGHNDkZrb8CAa1abRaosM2yGOyiikEc= +github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk= +github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= @@ -414,19 +743,28 @@ github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= +github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= +github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= @@ -453,14 +791,19 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/thanhpk/randstr v1.0.6 h1:psAOktJFD4vV9NEVb3qkhRSMvYh4ORRaj1+w/hn4B+o= github.com/thanhpk/randstr v1.0.6/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb h1:Ywfo8sUltxogBpFuMOFRrrSifO788kAFxmvVw31PtQQ= +github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb/go.mod h1:ikPs9bRWicNw3S7XpJ8sK/smGwU9WcSVU3dy9qahYBM= github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/uptrace/bun v1.1.16 h1:cn9cgEMFwcyYRsQLfxCRMUxyK1WaHwOVrR3TvzEFZ/A= github.com/uptrace/bun v1.1.16/go.mod h1:7HnsMRRvpLFUcquJxp22JO8PsWKpFQO/gNXqqsuGWg8= github.com/uptrace/bun/dialect/pgdialect v1.1.16 h1:eUPZ+YCJ69BA+W1X1ZmpOJSkv1oYtinr0zCXf7zCo5g= @@ -469,10 +812,27 @@ github.com/uptrace/bun/driver/pgdriver v1.1.16 h1:b/NiSXk6Ldw7KLfMLbOqIkm4odHd7Q github.com/uptrace/bun/driver/pgdriver v1.1.16/go.mod h1:Rmfbc+7lx1z/umjMyAxkOHK81LgnGj71XC5YpA6k1vU= github.com/uptrace/bun/extra/bundebug v1.1.16 h1:SgicRQGtnjhrIhlYOxdkOm1Em4s6HykmT3JblHnoTBM= github.com/uptrace/bun/extra/bundebug v1.1.16/go.mod h1:SkiOkfUirBiO1Htc4s5bQKEq+JSeU1TkBVpMsPz2ePM= +github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/warpfork/go-testmark v0.12.1 h1:rMgCpJfwy1sJ50x0M0NgyphxYYPMOODIJHhsXyEHU0s= +github.com/warpfork/go-testmark v0.12.1/go.mod h1:kHwy7wfvGSPh1rQJYKayD4AbtNaeyZdcGi9tNJTaa5Y= +github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= +github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= +github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc h1:BCPnHtcboadS0DvysUuJXZ4lWVv5Bh5i7+tbIyi+ck4= +github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc/go.mod h1:r45hJU7yEoA81k6MWNhpMj/kms0n14dkzkxYHoB96UM= +github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 h1:5HZfQkwe0mIfyDmc1Em5GqlNRzcdtlv4HTNmdpt7XH0= +github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11/go.mod h1:Wlo/SzPmxVp6vXpGt/zaXhHH0fn4IxgqZc82aKg6bpQ= +github.com/whyrusleeping/cbor-gen v0.0.0-20240109153615-66e95c3e8a87 h1:S4wCk+ZL4WGGaI+GsmqCRyt68ISbnZWsK9dD9jYL0fA= +github.com/whyrusleeping/cbor-gen v0.0.0-20240109153615-66e95c3e8a87/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= +github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP9WYv2nzyawpKMOCl+Z/jW7djv2/J50lj9E= +github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f/go.mod h1:p9UJB6dDgdPgMJZs7UjUOdulKyRr9fqkS+6JKAInPy8= +github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= +github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= +github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 h1:E9S12nwJwEOXe2d6gT6qxdvqMnNq+VnSsKPgm2ZZNds= +github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7/go.mod h1:X2c0RVCI1eSUFI8eLcY3c0423ykwiUdxLJtkDvruhjI= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -487,6 +847,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -495,29 +856,59 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= +go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0 h1:VhlEQAPp9R1ktYfrPk5SOryw1e9LDDTZCbIPFrho0ec= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0/go.mod h1:kB3ufRbfU+CQ4MlUcqtW8Z7YEOBeK2DJ6CmR5rYYF3E= +go.opentelemetry.io/otel/exporters/zipkin v1.21.0 h1:D+Gv6lSfrFBWmQYyxKjDd0Zuld9SRXpIrEsKZvE4DO4= +go.opentelemetry.io/otel/exporters/zipkin v1.21.0/go.mod h1:83oMKR6DzmHisFOW3I+yIMGZUTjxiWaiBI8M8+TU5zE= +go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= +go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= +go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= +go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= -go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= +go.uber.org/dig v1.17.1 h1:Tga8Lz8PcYNsWsyHMZ1Vm0OQOUaJNDyvPImgbAu9YSc= +go.uber.org/dig v1.17.1/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= go.uber.org/fx v1.20.1 h1:zVwVQGS8zYvhh9Xxcu4w1M6ESyeMzebzj2NbSayZ4Mk= go.uber.org/fx v1.20.1/go.mod h1:iSYNbHf2y55acNCwCXKx7LbWb5WG1Bnue5RDXz1OREg= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= -go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= -go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= -go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= -go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +go4.org v0.0.0-20200411211856-f5505b9728dd/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg= +go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc= +go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= @@ -531,8 +922,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= +golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -547,6 +938,7 @@ golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPI golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -558,6 +950,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -594,6 +987,8 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -605,8 +1000,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= -golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= +golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= +golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -618,6 +1013,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -633,6 +1029,7 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -652,6 +1049,7 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -660,7 +1058,9 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -668,6 +1068,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -676,6 +1078,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -687,6 +1091,7 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -696,6 +1101,8 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -731,14 +1138,17 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= +gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -758,16 +1168,17 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.147.0 h1:Can3FaQo9LlVqxJCodNmeZW/ib3/qKAY3rFeXiHo5gc= -google.golang.org/api v0.147.0/go.mod h1:pQ/9j83DcmPd/5C9e2nFOdjjNkDZ1G+zkbK2uvdkJMs= +google.golang.org/api v0.149.0 h1:b2CqT6kG+zqJIVKRQ3ELJVLN1PwHZ6DJ3dW8yl82rgY= +google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -804,12 +1215,12 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 h1:SeZZZx0cP0fqUyA+oRzP9k7cSwJlvDFiROO72uwD6i0= -google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk= -google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU= -google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c h1:jHkCUWkseRf+W+edG5hMzr/Uh1xkDREY4caybAq4dpY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c/go.mod h1:4cYg8o5yUbm77w8ZX00LhMVNl/YVBFJRYWDc0uYWMs0= +google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 h1:nz5NESFLZbJGPFxDT/HCn+V1mZ8JGNoY4nUpmW/Y2eg= +google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917/go.mod h1:pZqR+glSb11aJ+JQcczCvgf47+duRuzNSKqE8YAQnV0= +google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1 h1:OPXtXn7fNMaXwO3JvOmF1QyTc00jsSFFz1vXXBOdCDo= +google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:B5xPO//w8qmBDjGReYLpR6UJPnkldGkCSMoH/2vxJeg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 h1:gphdwh0npgs8elJ4T6J+DQJHPVF7RsuJHCfwztUb4J4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -826,8 +1237,8 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= +google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -840,15 +1251,18 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -859,6 +1273,7 @@ gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= +gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -866,6 +1281,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= +lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo= mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= From bcc25bd1f0b41bfd7d127e148ec5a60610fd4dbf Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Thu, 22 Feb 2024 14:43:03 +0800 Subject: [PATCH 172/210] feat: update ci golang version --- .github/workflows/basic_check.yml | 2 +- .github/workflows/deployment.yml | 2 +- .github/workflows/test.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/basic_check.yml b/.github/workflows/basic_check.yml index 9716bdfa..2e68b60a 100644 --- a/.github/workflows/basic_check.yml +++ b/.github/workflows/basic_check.yml @@ -12,7 +12,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.20.9' + go-version: '1.22.0' cache: true - name: install deps diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index a30ca543..798e23e9 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.20.9' + go-version: '1.22.0' cache: false #post setup fail in local runner , disable for now - name: install deps diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b1de9a03..95d98012 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.20.9' + go-version: '1.22.0' cache: true - name: install deps From 7dd8af7bbf4b1d84ea069ce86cd8e9dd3d857f6e Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Thu, 22 Feb 2024 15:00:50 +0800 Subject: [PATCH 173/210] feat: make lint happy --- block/factory/build.go | 6 +++--- block/ipfs/adapter.go | 15 +++++++-------- block/ipfs/walker.go | 11 ++++------- block/params/block.go | 2 +- config/blockstore.go | 4 ++-- integrationtest/repo_test.go | 10 +++++----- makefile | 2 ++ 7 files changed, 24 insertions(+), 26 deletions(-) diff --git a/block/factory/build.go b/block/factory/build.go index aedb4456..d7322ccf 100644 --- a/block/factory/build.go +++ b/block/factory/build.go @@ -76,13 +76,13 @@ func BuildBlockAdapter(ctx context.Context, c params.AdapterConfig) (block.Adapt } func buildIpfsAdapter(_ context.Context, params params.Ipfs) (*ipfs.Adapter, error) { - adapter, err := ipfs.NewAdapter(params.Url) + adapter, err := ipfs.NewAdapter(params.URL) if err != nil { - return nil, fmt.Errorf("got error opening a local block adapter with path %s: %w", params.Url, err) + return nil, fmt.Errorf("got error opening a local block adapter with path %s: %w", params.URL, err) } log.With( "type", "local", - "url", params.Url, + "url", params.URL, ).Info("initialized blockstore adapter") return adapter, nil } diff --git a/block/ipfs/adapter.go b/block/ipfs/adapter.go index 4eaa0346..3c0ff397 100644 --- a/block/ipfs/adapter.go +++ b/block/ipfs/adapter.go @@ -65,7 +65,7 @@ func (qk QualifiedKey) GetKey() string { } func NewAdapter(url string, opts ...func(a *Adapter)) (*Adapter, error) { - addr, err := ma.NewMultiaddr(strings.TrimSpace(string(url))) + addr, err := ma.NewMultiaddr(strings.TrimSpace(url)) if err != nil { return nil, err } @@ -282,7 +282,7 @@ func (l *Adapter) GetRange(ctx context.Context, obj block.ObjectPointer, start i return rc, nil } -func (l *Adapter) GetProperties(_ context.Context, obj block.ObjectPointer) (block.Properties, error) { +func (l *Adapter) GetProperties(_ context.Context, _ block.ObjectPointer) (block.Properties, error) { return block.Properties{}, nil } @@ -317,7 +317,7 @@ func (l *Adapter) UploadPart(ctx context.Context, obj block.ObjectPointer, _ int }, err } -func (l *Adapter) AbortMultiPartUpload(_ context.Context, obj block.ObjectPointer, uploadID string) error { +func (l *Adapter) AbortMultiPartUpload(_ context.Context, _ block.ObjectPointer, uploadID string) error { if err := isValidUploadID(uploadID); err != nil { return err } @@ -325,18 +325,17 @@ func (l *Adapter) AbortMultiPartUpload(_ context.Context, obj block.ObjectPointe panic("todo") } -func (l *Adapter) CompleteMultiPartUpload(ctx context.Context, obj block.ObjectPointer, uploadID string, multipartList *block.MultipartUploadCompletion) (*block.CompleteMultiPartUploadResponse, error) { +func (l *Adapter) CompleteMultiPartUpload(_ context.Context, obj block.ObjectPointer, uploadID string, multipartList *block.MultipartUploadCompletion) (*block.CompleteMultiPartUploadResponse, error) { if err := isValidUploadID(uploadID); err != nil { return nil, err } - etag := computeETag(multipartList.Part) + "-" + strconv.Itoa(len(multipartList.Part)) size, err := l.unitePartFiles(obj, uploadID) if err != nil { return nil, fmt.Errorf("multipart upload unite for %s: %w", uploadID, err) } return &block.CompleteMultiPartUploadResponse{ - ETag: etag, + ETag: computeETag(multipartList.Part) + "-" + strconv.Itoa(len(multipartList.Part)), ContentLength: size, }, nil } @@ -354,11 +353,11 @@ func computeETag(parts []block.MultipartPart) string { return csm } -func (l *Adapter) unitePartFiles(identifier block.ObjectPointer, uploadID string) (int64, error) { +func (l *Adapter) unitePartFiles(_ block.ObjectPointer, _ string) (int64, error) { panic("not impl") } -func (l *Adapter) removePartFiles(ctx context.Context, files []string) error { +func (l *Adapter) removePartFiles(ctx context.Context, files []string) error { //nolint var firstErr error for _, name := range files { // If removal fails prefer to skip the error: "only" wasted space. diff --git a/block/ipfs/walker.go b/block/ipfs/walker.go index 15a27b16..8d84e99f 100644 --- a/block/ipfs/walker.go +++ b/block/ipfs/walker.go @@ -3,19 +3,17 @@ package ipfs import ( "context" "fmt" - "github.com/modern-go/reflect2" + "net/url" "sort" "strings" - iface "github.com/ipfs/kubo/core/coreiface" - - "github.com/ipfs/kubo/core/coreiface/options" - "github.com/ipfs/boxo/path" - "github.com/ipfs/kubo/client/rpc" + iface "github.com/ipfs/kubo/core/coreiface" + "github.com/ipfs/kubo/core/coreiface/options" "github.com/jiaozifs/jiaozifs/block" + "github.com/modern-go/reflect2" ) type Walker struct { @@ -31,7 +29,6 @@ func NewIPFSWalker(client *rpc.HttpApi) *Walker { } func (s *Walker) Walk(ctx context.Context, storageURI *url.URL, op block.WalkOptions, walkFn func(e block.ObjectStoreEntry) error) error { - const maxKeys = 1000 prefix := strings.TrimLeft(storageURI.Path, "/") curPath, err := path.NewPath(prefix) diff --git a/block/params/block.go b/block/params/block.go index 50b93406..814b3718 100644 --- a/block/params/block.go +++ b/block/params/block.go @@ -17,7 +17,7 @@ type AdapterConfig interface { type Mem struct{} type Ipfs struct { - Url string + URL string } type Local struct { diff --git a/config/blockstore.go b/config/blockstore.go index 6535b60f..a7ffd2d5 100644 --- a/config/blockstore.go +++ b/config/blockstore.go @@ -19,7 +19,7 @@ type BlockStoreConfig struct { AllowedExternalPrefixes []string `mapstructure:"allowed_external_prefixes" json:"allowed_external_prefixes"` } `mapstructure:"local" json:"local"` Ipfs *struct { - Url string `mapstructure:"url" json:"url"` + URL string `mapstructure:"url" json:"url"` } `mapstructure:"ipfs" json:"ipfs"` S3 *struct { S3AuthInfo `mapstructure:",squash"` @@ -67,7 +67,7 @@ func (c *BlockStoreConfig) BlockstoreType() string { func (c *BlockStoreConfig) BlockstoreIpfsParams() (params.Ipfs, error) { return params.Ipfs{ - Url: c.Ipfs.Url, + URL: c.Ipfs.URL, }, nil } diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index dfcecfa4..39861142 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -447,31 +447,31 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors client.RequestEditors = nil - resp, err := client.DeleteRepository(ctx, userName, repoName) + resp, err := client.DeleteRepository(ctx, userName, repoName, &api.DeleteRepositoryParams{}) client.RequestEditors = re convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("delete repository in not exit repo", func() { - resp, err := client.DeleteRepository(ctx, userName, "happyrunfake") + resp, err := client.DeleteRepository(ctx, userName, "happyrunfake", &api.DeleteRepositoryParams{}) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) }) c.Convey("delete repository in non exit user", func() { - resp, err := client.DeleteRepository(ctx, "telo", repoName) + resp, err := client.DeleteRepository(ctx, "telo", repoName, &api.DeleteRepositoryParams{}) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) }) c.Convey("delete repository in other's repo", func() { - resp, err := client.DeleteRepository(ctx, "admin", repoName) + resp, err := client.DeleteRepository(ctx, "admin", repoName, &api.DeleteRepositoryParams{}) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) }) c.Convey("delete repository successful", func() { - resp, err := client.DeleteRepository(ctx, userName, repoName) + resp, err := client.DeleteRepository(ctx, userName, repoName, &api.DeleteRepositoryParams{}) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) diff --git a/makefile b/makefile index a43e12a3..e93ab23a 100644 --- a/makefile +++ b/makefile @@ -23,6 +23,8 @@ SWAGGER_ARG= swagger-srv: swagger serve $(SWAGGER_ARG) -F swagger ./api/swagger.yml +lint: + golang-lint run ./... test: gen-api go test -timeout=30m -parallel=4 -v ./... build:gen-api From 31e66ea96934ddfc43e9fb41d29eb1d449ddd7d5 Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Tue, 16 Jan 2024 11:43:54 +0800 Subject: [PATCH 174/210] add jiaozifs_aim.drawio --- docs/jiaozifs_aim.drawio | 266 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 docs/jiaozifs_aim.drawio diff --git a/docs/jiaozifs_aim.drawio b/docs/jiaozifs_aim.drawio new file mode 100644 index 00000000..99d8c6a0 --- /dev/null +++ b/docs/jiaozifs_aim.drawio @@ -0,0 +1,266 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 73a13d0fbca9fd07013826ee5b81b74e0ec9a654 Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Tue, 16 Jan 2024 11:44:38 +0800 Subject: [PATCH 175/210] add jiaozifs_aim.png --- docs/jiaozifs_aim.png | Bin 0 -> 173973 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/jiaozifs_aim.png diff --git a/docs/jiaozifs_aim.png b/docs/jiaozifs_aim.png new file mode 100644 index 0000000000000000000000000000000000000000..1008a72006cb1736e84a7b7818ef0dc0a9ae8739 GIT binary patch literal 173973 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 vfY Date: Tue, 16 Jan 2024 13:45:50 +0800 Subject: [PATCH 176/210] Update .fend.yaml --- .fend.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.fend.yaml b/.fend.yaml index cf67ebc3..0bde45fc 100644 --- a/.fend.yaml +++ b/.fend.yaml @@ -6,5 +6,6 @@ skip: - jzfs extension: - .input + - .png - .output - .jpg From 324bace0a6245d705144ec53f5a02638e8b26ad1 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Thu, 22 Feb 2024 15:43:31 +0800 Subject: [PATCH 177/210] feat: health check api --- api/api_impl/server.go | 7 +++++++ go.mod | 3 ++- go.sum | 4 ++++ integrationtest/root_test.go | 2 ++ integrationtest/status_test.go | 20 ++++++++++++++++++++ 5 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 integrationtest/status_test.go diff --git a/api/api_impl/server.go b/api/api_impl/server.go index e3426605..01f40c10 100644 --- a/api/api_impl/server.go +++ b/api/api_impl/server.go @@ -9,6 +9,8 @@ import ( "net/url" "strings" + "github.com/hellofresh/health-go/v5" + "github.com/getkin/kin-openapi/openapi3" "github.com/getkin/kin-openapi/routers/gorillamux" @@ -74,6 +76,11 @@ func SetupAPI(lc fx.Lifecycle, apiConfig *config.APIConfig, secretStore crypt.Se api.HandlerFromMuxWithBaseURL(controller, apiRouter, APIV1Prefix) r.Handle("/api/docs/*", http.StripPrefix("/api/docs", swaggerui.Handler(raw))) + h, _ := health.New(health.WithComponent(health.Component{ + Name: "myservice", + Version: "v1.0", + })) + r.Get("/status", h.HandlerFunc) url, err := url.Parse(apiConfig.Listen) if err != nil { diff --git a/go.mod b/go.mod index 2bd6c61e..26b34887 100644 --- a/go.mod +++ b/go.mod @@ -117,7 +117,7 @@ require ( github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-sql-driver/mysql v1.7.0 // indirect + github.com/go-sql-driver/mysql v1.7.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect @@ -134,6 +134,7 @@ require ( github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hellofresh/health-go/v5 v5.5.2 // indirect github.com/imdario/mergo v0.3.15 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/yaml v0.2.0 // indirect diff --git a/go.sum b/go.sum index 9872177b..4f120811 100644 --- a/go.sum +++ b/go.sum @@ -241,6 +241,8 @@ github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogB github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= @@ -369,6 +371,8 @@ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hellofresh/health-go/v5 v5.5.2 h1:h17lycd6foR2AUcd2tLYRKyU9YM7oyVtTAqUDqcM5JE= +github.com/hellofresh/health-go/v5 v5.5.2/go.mod h1:+ENHPehFhTo9xwpJ/vQIe4iP2Uh3a4fUIcA6Rl8DqJ0= github.com/hnlq715/golang-lru v0.4.0 h1:gyo/wIvLE6Upf1wucAfwTjpR+BQ5Lli2766H2MnNPv0= github.com/hnlq715/golang-lru v0.4.0/go.mod h1:RBkgDAtlu0SgTPvpb4VW2/RQnkCBMRD3Lr6B9RhsAS8= github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8= diff --git a/integrationtest/root_test.go b/integrationtest/root_test.go index 407a3c6d..918e082b 100644 --- a/integrationtest/root_test.go +++ b/integrationtest/root_test.go @@ -12,6 +12,8 @@ func TestSpec(t *testing.T) { urlStr, cancel := SetupDaemon(t, ctx) defer cancel() + convey.Convey("status test", t, StatusSpec(ctx, urlStr)) + convey.Convey("user test", t, UserSpec(ctx, urlStr)) convey.Convey("repo test", t, RepoSpec(ctx, urlStr)) convey.Convey("branch test", t, BranchSpec(ctx, urlStr)) diff --git a/integrationtest/status_test.go b/integrationtest/status_test.go new file mode 100644 index 00000000..38eb7393 --- /dev/null +++ b/integrationtest/status_test.go @@ -0,0 +1,20 @@ +package integrationtest + +import ( + "context" + "net/http" + "net/url" + + "github.com/smartystreets/goconvey/convey" +) + +func StatusSpec(ctx context.Context, urlStr string) func(c convey.C) { + return func(c convey.C) { + url, err := url.Parse(urlStr) + convey.ShouldBeNil(err) + url.Path = "/status" + resp, err := http.Get(url.String()) + convey.ShouldBeNil(err) + convey.ShouldEqual(resp.StatusCode, http.StatusOK) + } +} From 56720bcbbd8d8e14d8808b28770eff325964acdf Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Thu, 22 Feb 2024 15:48:53 +0800 Subject: [PATCH 178/210] chore: lint --- integrationtest/status_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrationtest/status_test.go b/integrationtest/status_test.go index 38eb7393..2f3b18f3 100644 --- a/integrationtest/status_test.go +++ b/integrationtest/status_test.go @@ -8,7 +8,7 @@ import ( "github.com/smartystreets/goconvey/convey" ) -func StatusSpec(ctx context.Context, urlStr string) func(c convey.C) { +func StatusSpec(_ context.Context, urlStr string) func(c convey.C) { return func(c convey.C) { url, err := url.Parse(urlStr) convey.ShouldBeNil(err) From b55677207ec694a4805f24246892ce35ae50c5c9 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Thu, 22 Feb 2024 18:23:09 +0800 Subject: [PATCH 179/210] feat: implement aksk gen sign and verify --- auth/aksk/gen.go | 22 +++++ auth/aksk/sign.go | 87 ++++++++++++++++++ auth/aksk/verifier.go | 105 +++++++++++++++++++++ auth/aksk/verifier_test.go | 183 +++++++++++++++++++++++++++++++++++++ auth/auth_middleware.go | 4 +- 5 files changed, 399 insertions(+), 2 deletions(-) create mode 100644 auth/aksk/gen.go create mode 100644 auth/aksk/sign.go create mode 100644 auth/aksk/verifier.go create mode 100644 auth/aksk/verifier_test.go diff --git a/auth/aksk/gen.go b/auth/aksk/gen.go new file mode 100644 index 00000000..fc0dd8c7 --- /dev/null +++ b/auth/aksk/gen.go @@ -0,0 +1,22 @@ +package aksk + +import ( + "crypto/rand" + "encoding/hex" + "io" +) + +func GenerateAksk() (string, string, error) { + akBytes, err := io.ReadAll(io.LimitReader(rand.Reader, 16)) + if err != nil { + return "", "", err + } + ak := hex.EncodeToString(akBytes) + + skBytes, err := io.ReadAll(io.LimitReader(rand.Reader, 16)) + if err != nil { + return "", "", err + } + sk := hex.EncodeToString(skBytes) + return ak, sk, nil +} diff --git a/auth/aksk/sign.go b/auth/aksk/sign.go new file mode 100644 index 00000000..9ffcdec7 --- /dev/null +++ b/auth/aksk/sign.go @@ -0,0 +1,87 @@ +package aksk + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + "net/http" + "net/url" + "sort" + "strings" + "time" +) + +const ( + signatureVersion = "0" + signatureMethod = "HmacSHA256" + timeFormat = "2006-01-02T15:04:05Z" +) + +type Signer interface { + Sign(req *http.Request) error +} + +var _ Signer = (*V0Signer)(nil) + +type V0Signer struct { + accessKey, secretKey string +} + +func NewV0Signer(accessKey string, secretKey string) *V0Signer { + return &V0Signer{accessKey: accessKey, secretKey: secretKey} +} + +func (voSigner V0Signer) Sign(req *http.Request) error { + // http verb + + curTime := time.Now() + // set query parameter + query := req.URL.Query() + query.Set("AWSAccessKeyId", voSigner.accessKey) + query.Set("SignatureVersion", signatureVersion) + query.Set("SignatureMethod", signatureMethod) + query.Set("Timestamp", curTime.UTC().Format(timeFormat)) + + req.Header.Del("Signature") + + method := req.Method + host := req.URL.Host + path := req.URL.Path + if path == "" { + path = "/" + } + + // obtain all of the query keys and sort them + queryKeys := make([]string, 0, len(query)) + for key := range query { + queryKeys = append(queryKeys, key) + } + sort.Strings(queryKeys) + + // build URL-encoded query keys and values + queryKeysAndValues := make([]string, len(queryKeys)) + for i, key := range queryKeys { + k := strings.Replace(url.QueryEscape(key), "+", "%20", -1) + v := strings.Replace(url.QueryEscape(query.Get(key)), "+", "%20", -1) + queryKeysAndValues[i] = k + "=" + v + } + + // join into one query string + queryString := strings.Join(queryKeysAndValues, "&") + + // build the canonical string for the V2 signature + stringToSign := strings.Join([]string{ + method, + host, + path, + queryString, + }, "\n") + + hash := hmac.New(sha256.New, []byte(voSigner.secretKey)) + hash.Write([]byte(stringToSign)) + signature := base64.StdEncoding.EncodeToString(hash.Sum(nil)) + query.Set("Signature", signature) + + req.URL.RawQuery = query.Encode() + return nil +} diff --git a/auth/aksk/verifier.go b/auth/aksk/verifier.go new file mode 100644 index 00000000..792cd8d6 --- /dev/null +++ b/auth/aksk/verifier.go @@ -0,0 +1,105 @@ +package aksk + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + "fmt" + "net/http" + "net/url" + "sort" + "strings" + "time" +) + +type V0Verifier interface { + Verify(req *http.Request) error +} + +type SkGetter interface { + Get(ak string) (string, error) +} + +var _ V0Verifier = (*V0Verier)(nil) + +type V0Verier struct { + skGetter SkGetter +} + +func NewV0Verier(skGetter SkGetter) *V0Verier { + return &V0Verier{skGetter: skGetter} +} + +func (v *V0Verier) Verify(req *http.Request) error { + query := req.URL.Query() + accessKey := query.Get("AWSAccessKeyId") + if len(accessKey) == 0 { + return fmt.Errorf("ak not found") + } + + secretKey, err := v.skGetter.Get(accessKey) + if err != nil { + return fmt.Errorf("access key not correct") + } + + sigMethod := query.Get("SignatureMethod") + if sigMethod != signatureMethod { + return fmt.Errorf("invalid signature method %s", sigMethod) + } + + sigVersion := query.Get("SignatureVersion") + if sigVersion != signatureVersion { + return fmt.Errorf("invalid signature method %s", sigMethod) + } + + reqTime := query.Get("Timestamp") + t, err := time.Parse(timeFormat, reqTime) + if err != nil { + return fmt.Errorf("invalid timestamp %s", reqTime) + } + if t.Before(time.Now().Add(-5 * time.Minute)) { + return fmt.Errorf("request is out of data") + } + expectSignature := query.Get("Signature") + query.Del("Signature") + + method := req.Method + host := req.URL.Host + path := req.URL.Path + if path == "" { + path = "/" + } + + // obtain all of the query keys and sort them + queryKeys := make([]string, 0, len(query)) + for key := range query { + queryKeys = append(queryKeys, key) + } + sort.Strings(queryKeys) + + // build URL-encoded query keys and values + queryKeysAndValues := make([]string, len(queryKeys)) + for i, key := range queryKeys { + k := strings.Replace(url.QueryEscape(key), "+", "%20", -1) + v := strings.Replace(url.QueryEscape(query.Get(key)), "+", "%20", -1) + queryKeysAndValues[i] = k + "=" + v + } + + // join into one query string + queryString := strings.Join(queryKeysAndValues, "&") + + // build the canonical string for the V2 signature + stringToSign := strings.Join([]string{ + method, + host, + path, + queryString, + }, "\n") + hash := hmac.New(sha256.New, []byte(secretKey)) + hash.Write([]byte(stringToSign)) + actualSig := base64.StdEncoding.EncodeToString(hash.Sum(nil)) + if actualSig != expectSignature { + return fmt.Errorf("signature not correct") + } + return nil +} diff --git a/auth/aksk/verifier_test.go b/auth/aksk/verifier_test.go new file mode 100644 index 00000000..54749811 --- /dev/null +++ b/auth/aksk/verifier_test.go @@ -0,0 +1,183 @@ +package aksk + +import ( + crand "crypto/rand" + "encoding/hex" + "io" + "math/rand" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestFull(t *testing.T) { + ak, sk, err := GenerateAksk() + require.NoError(t, err) + + t.Run("correct", func(t *testing.T) { + req := mockHttpRequest() + signer := NewV0Signer(ak, sk) + verifer := NewV0Verier(skGetter{sk}) + + err = signer.Sign(req) + require.NoError(t, err) + + err = verifer.Verify(req) + require.NoError(t, err) + }) + + t.Run("fail verify", func(t *testing.T) { + req := mockHttpRequest() + signer := NewV0Signer(ak, sk) + verifer := NewV0Verier(skGetter{sk}) + + err = signer.Sign(req) + require.NoError(t, err) + + query := req.URL.Query() + query.Add("a", "b") + req.URL.RawQuery = query.Encode() + err = verifer.Verify(req) + require.Error(t, err) + }) + t.Run("no access id", func(t *testing.T) { + req := mockHttpRequest() + signer := NewV0Signer(ak, sk) + verifer := NewV0Verier(skGetter{sk}) + + err = signer.Sign(req) + require.NoError(t, err) + + query := req.URL.Query() + query.Del("AWSAccessKeyId") + req.URL.RawQuery = query.Encode() + err = verifer.Verify(req) + require.Error(t, err) + }) + t.Run("sig method fail", func(t *testing.T) { + req := mockHttpRequest() + signer := NewV0Signer(ak, sk) + verifer := NewV0Verier(skGetter{sk}) + + err = signer.Sign(req) + require.NoError(t, err) + + query := req.URL.Query() + query.Set("SignatureMethod", "2") + req.URL.RawQuery = query.Encode() + err = verifer.Verify(req) + require.Error(t, err) + }) + t.Run("sig method fail", func(t *testing.T) { + req := mockHttpRequest() + signer := NewV0Signer(ak, sk) + verifer := NewV0Verier(skGetter{sk}) + + err = signer.Sign(req) + require.NoError(t, err) + + query := req.URL.Query() + query.Set("SignatureMethod", "md5") + req.URL.RawQuery = query.Encode() + err = verifer.Verify(req) + require.Error(t, err) + }) + t.Run("sig version fail", func(t *testing.T) { + req := mockHttpRequest() + signer := NewV0Signer(ak, sk) + verifer := NewV0Verier(skGetter{sk}) + + err = signer.Sign(req) + require.NoError(t, err) + + query := req.URL.Query() + query.Set("SignatureVersion", "2") + req.URL.RawQuery = query.Encode() + err = verifer.Verify(req) + require.Error(t, err) + }) + t.Run("no timestamp", func(t *testing.T) { + req := mockHttpRequest() + signer := NewV0Signer(ak, sk) + verifer := NewV0Verier(skGetter{sk}) + + err = signer.Sign(req) + require.NoError(t, err) + + query := req.URL.Query() + query.Del("Timestamp") + req.URL.RawQuery = query.Encode() + err = verifer.Verify(req) + require.Error(t, err) + }) + + t.Run("invalid timestamp format", func(t *testing.T) { + req := mockHttpRequest() + signer := NewV0Signer(ak, sk) + verifer := NewV0Verier(skGetter{sk}) + + err = signer.Sign(req) + require.NoError(t, err) + + query := req.URL.Query() + query.Set("Timestamp", time.Now().String()) + req.URL.RawQuery = query.Encode() + err = verifer.Verify(req) + require.Error(t, err) + }) + t.Run("request out of date", func(t *testing.T) { + req := mockHttpRequest() + signer := NewV0Signer(ak, sk) + verifer := NewV0Verier(skGetter{sk}) + + err = signer.Sign(req) + require.NoError(t, err) + + query := req.URL.Query() + query.Set("Timestamp", time.Now().Add(-time.Minute*10).UTC().Format(timeFormat)) + req.URL.RawQuery = query.Encode() + err = verifer.Verify(req) + require.Error(t, err) + }) +} + +type skGetter struct { + sk string +} + +func (getter skGetter) Get(ak string) (string, error) { + return getter.sk, nil +} + +func mockHttpRequest() *http.Request { + verbs := []string{"GET", "POST", "PUT", "Delete"} + req, _ := http.NewRequest(verbs[rand.Intn(3)], "http://www.xx.com/index.html", closerWraper{io.LimitReader(crand.Reader, 100)}) + + query := req.URL.Query() + for i := 0; i < 3; i++ { + query.Set(randString(), randString()) + } + req.URL.RawQuery = query.Encode() + return req +} + +var _ io.ReadCloser = (*closerWraper)(nil) + +type closerWraper struct { + reader io.Reader +} + +func (c closerWraper) Read(p []byte) (int, error) { + return c.reader.Read(p) +} + +func (c closerWraper) Close() error { + return nil +} + +func randString() string { + akBytes, _ := io.ReadAll(io.LimitReader(crand.Reader, 16)) + return hex.EncodeToString(akBytes) +} diff --git a/auth/auth_middleware.go b/auth/auth_middleware.go index 3611c378..2280f3ad 100644 --- a/auth/auth_middleware.go +++ b/auth/auth_middleware.go @@ -116,11 +116,11 @@ func checkSecurityRequirements(r *http.Request, user, err = userByToken(ctx, userRepo, secretStore.SharedSecret(), token) case "basic_auth": // validate using basic auth - accessKey, secretKey, ok := r.BasicAuth() + userName, password, ok := r.BasicAuth() if !ok { continue } - user, err = userByAuth(ctx, authenticator, userRepo, accessKey, secretKey) + user, err = userByAuth(ctx, authenticator, userRepo, userName, password) case "cookie_auth": var internalAuthSession *sessions.Session internalAuthSession, _ = sessionStore.Get(r, InternalAuthSessionName) From 84434dca04ea7cef23c0dba36ffdbdabf6e88e10 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Fri, 23 Feb 2024 11:06:29 +0800 Subject: [PATCH 180/210] feat: add aksk models --- go.mod | 3 +- go.sum | 3 +- models/aksk.go | 172 ++++++++++++++++++ models/aksk_test.go | 87 +++++++++ .../migrations/20210505110026_init_project.go | 7 + models/repo.go | 5 + 6 files changed, 273 insertions(+), 4 deletions(-) create mode 100644 models/aksk.go create mode 100644 models/aksk_test.go diff --git a/go.mod b/go.mod index 26b34887..96bda25b 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( github.com/google/uuid v1.5.0 github.com/gorilla/sessions v1.2.2 github.com/hashicorp/go-version v1.6.0 + github.com/hellofresh/health-go/v5 v5.5.2 github.com/hnlq715/golang-lru v0.4.0 github.com/ipfs/boxo v0.18.0 github.com/ipfs/go-log/v2 v2.5.1 @@ -117,7 +118,6 @@ require ( github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-sql-driver/mysql v1.7.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect @@ -134,7 +134,6 @@ require ( github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/hellofresh/health-go/v5 v5.5.2 // indirect github.com/imdario/mergo v0.3.15 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/yaml v0.2.0 // indirect diff --git a/go.sum b/go.sum index 4f120811..082a8553 100644 --- a/go.sum +++ b/go.sum @@ -239,8 +239,6 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= -github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= @@ -782,6 +780,7 @@ github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0 github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= diff --git a/models/aksk.go b/models/aksk.go new file mode 100644 index 00000000..61859f40 --- /dev/null +++ b/models/aksk.go @@ -0,0 +1,172 @@ +package models + +import ( + "context" + "time" + + "github.com/google/uuid" + "github.com/uptrace/bun" +) + +type AkSk struct { + bun.BaseModel `bun:"table:aksks"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()" json:"id"` + // UserID ak/sk belong to user id + UserID uuid.UUID `bun:"user_id,type:uuid,notnull" json:"user_id"` + // AccessKey + AccessKey string `bun:"access_key,unique,notnull" json:"access_key"` + // SecretKey + SecretKey string `bun:"secret_key,unique,notnull" json:"secret_key"` + // Description + Description *string `bun:"description" json:"description,omitempty"` + + CreatedAt time.Time `bun:"created_at,type:timestamp,notnull" json:"created_at"` + UpdatedAt time.Time `bun:"updated_at,type:timestamp,notnull" json:"updated_at"` +} + +type GetAkSkParams struct { + id uuid.UUID + accessKey *string +} + +func NewGetAkSkParams() *GetAkSkParams { + return &GetAkSkParams{} +} + +func (gap *GetAkSkParams) SetID(id uuid.UUID) *GetAkSkParams { + gap.id = id + return gap +} + +func (gap *GetAkSkParams) SetAccessKey(ak string) *GetAkSkParams { + gap.accessKey = &ak + return gap +} + +type ListAkSkParams struct { + userID uuid.UUID + after *time.Time + amount int +} + +func NewListAkSkParams() *ListAkSkParams { + return &ListAkSkParams{} +} + +func (lap *ListAkSkParams) SetUserID(userID uuid.UUID) *ListAkSkParams { + lap.userID = userID + return lap +} + +func (lap *ListAkSkParams) SetAfter(after time.Time) *ListAkSkParams { + lap.after = &after + return lap +} + +func (lap *ListAkSkParams) SetAmount(amount int) *ListAkSkParams { + lap.amount = amount + return lap +} + +type DeleteAkSkParams struct { + id uuid.UUID + accessKey *string +} + +func NewDeleteAkSkParams() *DeleteAkSkParams { + return &DeleteAkSkParams{} +} + +func (dap *DeleteAkSkParams) SetID(id uuid.UUID) *DeleteAkSkParams { + dap.id = id + return dap +} + +func (dap *DeleteAkSkParams) SetAccessKey(accessKey string) *DeleteAkSkParams { + dap.accessKey = &accessKey + return dap +} + +type IAkskRepo interface { + Insert(ctx context.Context, asSk *AkSk) (*AkSk, error) + Get(ctx context.Context, params *GetAkSkParams) (*AkSk, error) + + List(ctx context.Context, params *ListAkSkParams) ([]*AkSk, bool, error) + Delete(ctx context.Context, params *DeleteAkSkParams) (int64, error) +} + +var _ IAkskRepo = (*AkskRepo)(nil) + +type AkskRepo struct { + db bun.IDB +} + +func NewAkskRepo(db bun.IDB) IAkskRepo { + return &AkskRepo{db: db} +} + +func (a AkskRepo) Insert(ctx context.Context, akSk *AkSk) (*AkSk, error) { + _, err := a.db.NewInsert().Model(akSk).Exec(ctx) + if err != nil { + return nil, err + } + return akSk, nil +} + +func (a AkskRepo) Get(ctx context.Context, params *GetAkSkParams) (*AkSk, error) { + repo := &AkSk{} + query := a.db.NewSelect().Model(repo) + + if uuid.Nil != params.id { + query = query.Where("id = ?", params.id) + } + + if params.accessKey != nil { + query = query.Where("access_key = ?", *params.accessKey) + } + + err := query.Limit(1).Scan(ctx) + if err != nil { + return nil, err + } + return repo, nil +} + +func (a AkskRepo) List(ctx context.Context, params *ListAkSkParams) ([]*AkSk, bool, error) { + var branches []*AkSk + query := a.db.NewSelect().Model(&branches) + + if uuid.Nil != params.userID { + query = query.Where("user_Id = ?", params.userID) + } + + query = query.Order("created_at DESC") + if params.after != nil { + query = query.Where("created_at < ?", *params.after) + } + + err := query.Limit(params.amount).Scan(ctx) + return branches, len(branches) == params.amount, err +} + +func (a AkskRepo) Delete(ctx context.Context, params *DeleteAkSkParams) (int64, error) { + query := a.db.NewDelete().Model((*AkSk)(nil)) + + if uuid.Nil != params.id { + query = query.Where("id = ?", params.id) + } + + if params.accessKey != nil { + query = query.Where("access_key = ?", *params.accessKey) + } + + sqlResult, err := query.Exec(ctx) + if err != nil { + return 0, err + } + affectedRows, err := sqlResult.RowsAffected() + if err != nil { + return 0, err + } + return affectedRows, err +} diff --git a/models/aksk_test.go b/models/aksk_test.go new file mode 100644 index 00000000..802c7286 --- /dev/null +++ b/models/aksk_test.go @@ -0,0 +1,87 @@ +package models_test + +import ( + "context" + "testing" + + "github.com/google/uuid" + + "github.com/brianvoe/gofakeit/v6" + "github.com/google/go-cmp/cmp" + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/stretchr/testify/require" +) + +func TestAkskRepo_Delete(t *testing.T) { + ctx := context.Background() + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() + + repo := models.NewAkskRepo(db) + + t.Run("insert and get ", func(t *testing.T) { + akskModel := &models.AkSk{} + require.NoError(t, gofakeit.Struct(akskModel)) + + aksk, err := repo.Insert(ctx, akskModel) + require.NoError(t, err) + + expectAksk, err := repo.Get(ctx, models.NewGetAkSkParams().SetAccessKey(aksk.AccessKey).SetID(aksk.ID)) + require.NoError(t, err) + require.True(t, cmp.Equal(expectAksk, aksk, dbTimeCmpOpt)) + }) + t.Run("list", func(t *testing.T) { + userID := uuid.New() + for i := 0; i < 5; i++ { + akskModel := &models.AkSk{} + require.NoError(t, gofakeit.Struct(akskModel)) + akskModel.UserID = userID + _, err := repo.Insert(ctx, akskModel) + require.NoError(t, err) + } + + userID = uuid.New() + for i := 0; i < 5; i++ { + akskModel := &models.AkSk{} + require.NoError(t, gofakeit.Struct(akskModel)) + akskModel.UserID = userID + _, err := repo.Insert(ctx, akskModel) + require.NoError(t, err) + } + + aksks, hasMore, err := repo.List(ctx, models.NewListAkSkParams().SetUserID(userID)) + require.NoError(t, err) + require.False(t, hasMore) + require.Len(t, aksks, 5) + + aksks, hasMore, err = repo.List(ctx, models.NewListAkSkParams().SetUserID(userID).SetAmount(2)) + require.NoError(t, err) + require.True(t, hasMore) + require.Len(t, aksks, 2) + }) + + t.Run("delete by id", func(t *testing.T) { + akskModel := &models.AkSk{} + require.NoError(t, gofakeit.Struct(akskModel)) + + aksk, err := repo.Insert(ctx, akskModel) + require.NoError(t, err) + + deleteRows, err := repo.Delete(ctx, models.NewDeleteAkSkParams().SetID(aksk.ID)) + require.NoError(t, err) + require.Equal(t, int64(1), deleteRows) + }) + + t.Run("delete by ak", func(t *testing.T) { + akskModel := &models.AkSk{} + require.NoError(t, gofakeit.Struct(akskModel)) + + aksk, err := repo.Insert(ctx, akskModel) + require.NoError(t, err) + + deleteRows, err := repo.Delete(ctx, models.NewDeleteAkSkParams().SetAccessKey(aksk.AccessKey)) + require.NoError(t, err) + require.Equal(t, int64(1), deleteRows) + }) +} diff --git a/models/migrations/20210505110026_init_project.go b/models/migrations/20210505110026_init_project.go index 4b28923f..a9b00661 100644 --- a/models/migrations/20210505110026_init_project.go +++ b/models/migrations/20210505110026_init_project.go @@ -80,6 +80,13 @@ func init() { if err != nil { return err } + //aksk + _, err = db.NewCreateTable(). + Model((*models.AkSk)(nil)). + Exec(ctx) + if err != nil { + return err + } return err }, nil) } diff --git a/models/repo.go b/models/repo.go index 6a6bbafa..2a88194e 100644 --- a/models/repo.go +++ b/models/repo.go @@ -35,6 +35,7 @@ type IRepo interface { BranchRepo() IBranchRepo RepositoryRepo() IRepositoryRepo WipRepo() IWipRepo + AkskRepo() IAkskRepo } type PgRepo struct { @@ -92,3 +93,7 @@ func (repo *PgRepo) RepositoryRepo() IRepositoryRepo { func (repo *PgRepo) WipRepo() IWipRepo { return NewWipRepo(repo.db) } + +func (repo *PgRepo) AkskRepo() IAkskRepo { + return NewAkskRepo(repo.db) +} From cbba69409d6452bdd612997fe2e551673ea8feda Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Fri, 23 Feb 2024 11:45:54 +0800 Subject: [PATCH 181/210] feat: add aksk api support --- api/api_impl/impl.go | 1 + api/jiaozifs.gen.go | 964 +++++++++++++++++++++++++++++++++++++---- api/swagger.yml | 149 +++++++ controller/aksk_ctl.go | 149 +++++++ models/aksk.go | 19 +- models/aksk_test.go | 6 +- 6 files changed, 1195 insertions(+), 93 deletions(-) create mode 100644 controller/aksk_ctl.go diff --git a/api/api_impl/impl.go b/api/api_impl/impl.go index 5b44bab4..3a3a2c24 100644 --- a/api/api_impl/impl.go +++ b/api/api_impl/impl.go @@ -19,4 +19,5 @@ type APIController struct { controller.RepositoryController controller.BranchController controller.MergeRequestController + controller.AkSkController } diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index db863f8f..103a9983 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -55,6 +55,22 @@ const ( NotInitialized SetupStateState = "not_initialized" ) +// Aksk defines model for Aksk. +type Aksk struct { + AccessKey string `json:"access_key"` + CreatedAt int64 `json:"created_at"` + Description *string `json:"description,omitempty"` + Id openapi_types.UUID `json:"id"` + SecretKey string `json:"secret_key"` + UpdatedAt int64 `json:"updated_at"` +} + +// AkskList defines model for AkskList. +type AkskList struct { + Pagination Pagination `json:"pagination"` + Results []Aksk `json:"results"` +} + // AuthenticationToken defines model for AuthenticationToken. type AuthenticationToken struct { // Token a JWT token that could be used to authenticate requests @@ -505,6 +521,32 @@ type GetEntriesInRefParams struct { Type RefType `form:"type" json:"type"` } +// DeleteAkskParams defines parameters for DeleteAksk. +type DeleteAkskParams struct { + Id *openapi_types.UUID `form:"id,omitempty" json:"id,omitempty"` + AccessKey *string `form:"access_key,omitempty" json:"access_key,omitempty"` +} + +// GetAkskParams defines parameters for GetAksk. +type GetAkskParams struct { + Id *openapi_types.UUID `form:"id,omitempty" json:"id,omitempty"` + AccessKey *string `form:"access_key,omitempty" json:"access_key,omitempty"` +} + +// CreateAkskParams defines parameters for CreateAksk. +type CreateAkskParams struct { + Description *string `form:"description,omitempty" json:"description,omitempty"` +} + +// ListAksksParams defines parameters for ListAksks. +type ListAksksParams struct { + // After return items after this value + After *PaginationInt64After `form:"after,omitempty" json:"after,omitempty"` + + // Amount how many items to return + Amount *PaginationAmount `form:"amount,omitempty" json:"amount,omitempty"` +} + // ListRepositoryOfAuthenticatedUserParams defines parameters for ListRepositoryOfAuthenticatedUser. type ListRepositoryOfAuthenticatedUserParams struct { // Prefix return items prefixed with this value @@ -758,6 +800,18 @@ type ClientInterface interface { // GetSetupState request GetSetupState(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // DeleteAksk request + DeleteAksk(ctx context.Context, params *DeleteAkskParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetAksk request + GetAksk(ctx context.Context, params *GetAkskParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateAksk request + CreateAksk(ctx context.Context, params *CreateAkskParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // ListAksks request + ListAksks(ctx context.Context, params *ListAksksParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // RefreshToken request RefreshToken(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -1155,6 +1209,54 @@ func (c *Client) GetSetupState(ctx context.Context, reqEditors ...RequestEditorF return c.Client.Do(req) } +func (c *Client) DeleteAksk(ctx context.Context, params *DeleteAkskParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteAkskRequest(c.Server, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetAksk(ctx context.Context, params *GetAkskParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetAkskRequest(c.Server, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateAksk(ctx context.Context, params *CreateAkskParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateAkskRequest(c.Server, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) ListAksks(ctx context.Context, params *ListAksksParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListAksksRequest(c.Server, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) RefreshToken(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewRefreshTokenRequest(c.Server) if err != nil { @@ -2863,6 +2965,250 @@ func NewGetSetupStateRequest(server string) (*http.Request, error) { return req, nil } +// NewDeleteAkskRequest generates requests for DeleteAksk +func NewDeleteAkskRequest(server string, params *DeleteAkskParams) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/users/aksk") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Id != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "id", runtime.ParamLocationQuery, *params.Id); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.AccessKey != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "access_key", runtime.ParamLocationQuery, *params.AccessKey); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetAkskRequest generates requests for GetAksk +func NewGetAkskRequest(server string, params *GetAkskParams) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/users/aksk") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Id != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "id", runtime.ParamLocationQuery, *params.Id); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.AccessKey != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "access_key", runtime.ParamLocationQuery, *params.AccessKey); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewCreateAkskRequest generates requests for CreateAksk +func NewCreateAkskRequest(server string, params *CreateAkskParams) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/users/aksk") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Description != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "description", runtime.ParamLocationQuery, *params.Description); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("POST", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewListAksksRequest generates requests for ListAksks +func NewListAksksRequest(server string, params *ListAksksParams) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/users/aksks") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.After != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "after", runtime.ParamLocationQuery, *params.After); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Amount != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "amount", runtime.ParamLocationQuery, *params.Amount); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewRefreshTokenRequest generates requests for RefreshToken func NewRefreshTokenRequest(server string) (*http.Request, error) { var err error @@ -3769,6 +4115,18 @@ type ClientWithResponsesInterface interface { // GetSetupStateWithResponse request GetSetupStateWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetSetupStateResponse, error) + // DeleteAkskWithResponse request + DeleteAkskWithResponse(ctx context.Context, params *DeleteAkskParams, reqEditors ...RequestEditorFn) (*DeleteAkskResponse, error) + + // GetAkskWithResponse request + GetAkskWithResponse(ctx context.Context, params *GetAkskParams, reqEditors ...RequestEditorFn) (*GetAkskResponse, error) + + // CreateAkskWithResponse request + CreateAkskWithResponse(ctx context.Context, params *CreateAkskParams, reqEditors ...RequestEditorFn) (*CreateAkskResponse, error) + + // ListAksksWithResponse request + ListAksksWithResponse(ctx context.Context, params *ListAksksParams, reqEditors ...RequestEditorFn) (*ListAksksResponse, error) + // RefreshTokenWithResponse request RefreshTokenWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*RefreshTokenResponse, error) @@ -4272,14 +4630,101 @@ func (r CompareCommitResponse) StatusCode() int { return 0 } -type GetEntriesInRefResponse struct { +type GetEntriesInRefResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]FullTreeEntry +} + +// Status returns HTTPResponse.Status +func (r GetEntriesInRefResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetEntriesInRefResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetSetupStateResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *SetupState +} + +// Status returns HTTPResponse.Status +func (r GetSetupStateResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetSetupStateResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type DeleteAkskResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r DeleteAkskResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DeleteAkskResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetAkskResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Aksk +} + +// Status returns HTTPResponse.Status +func (r GetAkskResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetAkskResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type CreateAkskResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *[]FullTreeEntry + JSON201 *Aksk } // Status returns HTTPResponse.Status -func (r GetEntriesInRefResponse) Status() string { +func (r CreateAkskResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -4287,21 +4732,21 @@ func (r GetEntriesInRefResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetEntriesInRefResponse) StatusCode() int { +func (r CreateAkskResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetSetupStateResponse struct { +type ListAksksResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *SetupState + JSON200 *AkskList } // Status returns HTTPResponse.Status -func (r GetSetupStateResponse) Status() string { +func (r ListAksksResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -4309,7 +4754,7 @@ func (r GetSetupStateResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetSetupStateResponse) StatusCode() int { +func (r ListAksksResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -4875,6 +5320,42 @@ func (c *ClientWithResponses) GetSetupStateWithResponse(ctx context.Context, req return ParseGetSetupStateResponse(rsp) } +// DeleteAkskWithResponse request returning *DeleteAkskResponse +func (c *ClientWithResponses) DeleteAkskWithResponse(ctx context.Context, params *DeleteAkskParams, reqEditors ...RequestEditorFn) (*DeleteAkskResponse, error) { + rsp, err := c.DeleteAksk(ctx, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteAkskResponse(rsp) +} + +// GetAkskWithResponse request returning *GetAkskResponse +func (c *ClientWithResponses) GetAkskWithResponse(ctx context.Context, params *GetAkskParams, reqEditors ...RequestEditorFn) (*GetAkskResponse, error) { + rsp, err := c.GetAksk(ctx, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetAkskResponse(rsp) +} + +// CreateAkskWithResponse request returning *CreateAkskResponse +func (c *ClientWithResponses) CreateAkskWithResponse(ctx context.Context, params *CreateAkskParams, reqEditors ...RequestEditorFn) (*CreateAkskResponse, error) { + rsp, err := c.CreateAksk(ctx, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateAkskResponse(rsp) +} + +// ListAksksWithResponse request returning *ListAksksResponse +func (c *ClientWithResponses) ListAksksWithResponse(ctx context.Context, params *ListAksksParams, reqEditors ...RequestEditorFn) (*ListAksksResponse, error) { + rsp, err := c.ListAksks(ctx, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseListAksksResponse(rsp) +} + // RefreshTokenWithResponse request returning *RefreshTokenResponse func (c *ClientWithResponses) RefreshTokenWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*RefreshTokenResponse, error) { rsp, err := c.RefreshToken(ctx, reqEditors...) @@ -5543,6 +6024,100 @@ func ParseGetSetupStateResponse(rsp *http.Response) (*GetSetupStateResponse, err return response, nil } +// ParseDeleteAkskResponse parses an HTTP response from a DeleteAkskWithResponse call +func ParseDeleteAkskResponse(rsp *http.Response) (*DeleteAkskResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DeleteAkskResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetAkskResponse parses an HTTP response from a GetAkskWithResponse call +func ParseGetAkskResponse(rsp *http.Response) (*GetAkskResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetAkskResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Aksk + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseCreateAkskResponse parses an HTTP response from a CreateAkskWithResponse call +func ParseCreateAkskResponse(rsp *http.Response) (*CreateAkskResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateAkskResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest Aksk + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil +} + +// ParseListAksksResponse parses an HTTP response from a ListAksksWithResponse call +func ParseListAksksResponse(rsp *http.Response) (*ListAksksResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListAksksResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest AkskList + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseRefreshTokenResponse parses an HTTP response from a RefreshTokenWithResponse call func ParseRefreshTokenResponse(rsp *http.Response) (*RefreshTokenResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -5938,6 +6513,18 @@ type ServerInterface interface { // check if jiaozifs setup // (GET /setup) GetSetupState(ctx context.Context, w *JiaozifsResponse, r *http.Request) + // delete aksk + // (DELETE /users/aksk) + DeleteAksk(ctx context.Context, w *JiaozifsResponse, r *http.Request, params DeleteAkskParams) + // get aksk + // (GET /users/aksk) + GetAksk(ctx context.Context, w *JiaozifsResponse, r *http.Request, params GetAkskParams) + // create aksk + // (POST /users/aksk) + CreateAksk(ctx context.Context, w *JiaozifsResponse, r *http.Request, params CreateAkskParams) + // list aksks + // (GET /users/aksks) + ListAksks(ctx context.Context, w *JiaozifsResponse, r *http.Request, params ListAksksParams) // refresh token for more time // (GET /users/refreshtoken) RefreshToken(ctx context.Context, w *JiaozifsResponse, r *http.Request) @@ -6123,6 +6710,30 @@ func (_ Unimplemented) GetSetupState(ctx context.Context, w *JiaozifsResponse, r w.WriteHeader(http.StatusNotImplemented) } +// delete aksk +// (DELETE /users/aksk) +func (_ Unimplemented) DeleteAksk(ctx context.Context, w *JiaozifsResponse, r *http.Request, params DeleteAkskParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get aksk +// (GET /users/aksk) +func (_ Unimplemented) GetAksk(ctx context.Context, w *JiaozifsResponse, r *http.Request, params GetAkskParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// create aksk +// (POST /users/aksk) +func (_ Unimplemented) CreateAksk(ctx context.Context, w *JiaozifsResponse, r *http.Request, params CreateAkskParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// list aksks +// (GET /users/aksks) +func (_ Unimplemented) ListAksks(ctx context.Context, w *JiaozifsResponse, r *http.Request, params ListAksksParams) { + w.WriteHeader(http.StatusNotImplemented) +} + // refresh token for more time // (GET /users/refreshtoken) func (_ Unimplemented) RefreshToken(ctx context.Context, w *JiaozifsResponse, r *http.Request) { @@ -7580,6 +8191,166 @@ func (siw *ServerInterfaceWrapper) GetSetupState(w http.ResponseWriter, r *http. handler.ServeHTTP(w, r.WithContext(ctx)) } +// DeleteAksk operation middleware +func (siw *ServerInterfaceWrapper) DeleteAksk(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params DeleteAkskParams + + // ------------- Optional query parameter "id" ------------- + + err = runtime.BindQueryParameter("form", true, false, "id", r.URL.Query(), ¶ms.Id) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + // ------------- Optional query parameter "access_key" ------------- + + err = runtime.BindQueryParameter("form", true, false, "access_key", r.URL.Query(), ¶ms.AccessKey) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "access_key", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteAksk(r.Context(), &JiaozifsResponse{w}, r, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// GetAksk operation middleware +func (siw *ServerInterfaceWrapper) GetAksk(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetAkskParams + + // ------------- Optional query parameter "id" ------------- + + err = runtime.BindQueryParameter("form", true, false, "id", r.URL.Query(), ¶ms.Id) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + // ------------- Optional query parameter "access_key" ------------- + + err = runtime.BindQueryParameter("form", true, false, "access_key", r.URL.Query(), ¶ms.AccessKey) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "access_key", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetAksk(r.Context(), &JiaozifsResponse{w}, r, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// CreateAksk operation middleware +func (siw *ServerInterfaceWrapper) CreateAksk(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params CreateAkskParams + + // ------------- Optional query parameter "description" ------------- + + err = runtime.BindQueryParameter("form", true, false, "description", r.URL.Query(), ¶ms.Description) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "description", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CreateAksk(r.Context(), &JiaozifsResponse{w}, r, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// ListAksks operation middleware +func (siw *ServerInterfaceWrapper) ListAksks(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params ListAksksParams + + // ------------- Optional query parameter "after" ------------- + + err = runtime.BindQueryParameter("form", true, false, "after", r.URL.Query(), ¶ms.After) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "after", Err: err}) + return + } + + // ------------- Optional query parameter "amount" ------------- + + err = runtime.BindQueryParameter("form", true, false, "amount", r.URL.Query(), ¶ms.Amount) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "amount", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListAksks(r.Context(), &JiaozifsResponse{w}, r, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // RefreshToken operation middleware func (siw *ServerInterfaceWrapper) RefreshToken(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -8423,6 +9194,18 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/setup", wrapper.GetSetupState) }) + r.Group(func(r chi.Router) { + r.Delete(options.BaseURL+"/users/aksk", wrapper.DeleteAksk) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/users/aksk", wrapper.GetAksk) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/users/aksk", wrapper.CreateAksk) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/users/aksks", wrapper.ListAksks) + }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/users/refreshtoken", wrapper.RefreshToken) }) @@ -8472,87 +9255,90 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index bbda5c77..7681ad4c 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -120,6 +120,42 @@ components: description: true if the comm prefs are missing. login_config: $ref: "#/components/schemas/LoginConfig" + AkskList: + type: object + required: + - pagination + - results + properties: + pagination: + $ref: "#/components/schemas/Pagination" + results: + type: array + items: + $ref: "#/components/schemas/Aksk" + Aksk: + type: object + required: + - id + - access_key + - secret_key + - created_at + - updated_at + properties: + id: + type: string + format: uuid + access_key: + type: string + secret_key: + type: string + description: + type: string + created_at: + type: integer + format: int64 + updated_at: + type: integer + format: int64 BranchCreation: type: object required: @@ -2150,3 +2186,116 @@ paths: description: too many requests default: description: Internal Server Error + + /users/aksk: + get: + tags: + - aksks + operationId: getAksk + summary: get aksk + parameters: + - in: query + name: id + allowEmptyValue: true + schema: + type: string + format: uuid + - in: query + name: access_key + allowEmptyValue: true + schema: + type: string + responses: + 200: + description: aksk + content: + application/json: + schema: + $ref: "#/components/schemas/Aksk" + 401: + description: Unauthorized + 404: + description: Resource Not Found + 420: + description: Too many requests + default: + description: Internal Server Error + post: + tags: + - aksks + operationId: createAksk + summary: create aksk + parameters: + - in: query + name: description + required: false + allowEmptyValue: true + schema: + type: string + responses: + 201: + description: aksk + content: + application/json: + schema: + $ref: "#/components/schemas/Aksk" + 401: + description: Unauthorized + 404: + description: Resource Not Found + 420: + description: Too many requests + default: + description: Internal Server Error + delete: + tags: + - aksks + operationId: deleteAksk + summary: delete aksk + parameters: + - in: query + name: id + allowEmptyValue: true + schema: + type: string + format: uuid + - in: query + name: access_key + allowEmptyValue: true + schema: + type: string + responses: + 200: + description: aksk + 401: + description: Unauthorized + 404: + description: Resource Not Found + 420: + description: Too many requests + default: + description: Internal Server Error + /users/aksks: + get: + tags: + - aksks + operationId: listAksks + summary: list aksks + parameters: + - $ref: "#/components/parameters/PaginationInt64After" + - $ref: "#/components/parameters/PaginationAmount" + responses: + 200: + description: aksk list + content: + application/json: + schema: + $ref: "#/components/schemas/AkskList" + 401: + description: Unauthorized + 404: + description: Resource Not Found + 420: + description: Too many requests + default: + description: Internal Server Error diff --git a/controller/aksk_ctl.go b/controller/aksk_ctl.go new file mode 100644 index 00000000..03f23f69 --- /dev/null +++ b/controller/aksk_ctl.go @@ -0,0 +1,149 @@ +package controller + +import ( + "context" + "net/http" + "time" + + "github.com/jiaozifs/jiaozifs/utils" + + aksk2 "github.com/jiaozifs/jiaozifs/auth/aksk" + + "github.com/jiaozifs/jiaozifs/auth" + + "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/models" + "go.uber.org/fx" +) + +type AkSkController struct { + fx.In + + Repo models.IAkskRepo +} + +func (akskCtl AkSkController) CreateAksk(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, params api.CreateAkskParams) { + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return + } + + ak, sk, err := aksk2.GenerateAksk() + if err != nil { + w.Error(err) + return + } + + aksk := &models.AkSk{ + UserID: operator.ID, + AccessKey: ak, + SecretKey: sk, + Description: params.Description, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + aksk, err = akskCtl.Repo.Insert(ctx, aksk) + if err != nil { + w.Error(err) + return + } + w.JSON(aksk, http.StatusCreated) +} + +func (akskCtl AkSkController) GetAksk(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, params api.GetAkskParams) { + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return + } + + getParams := models.NewGetAkSkParams().SetUserID(operator.ID) + if params.Id != nil { + getParams.SetID(*params.Id) + } + + if params.AccessKey != nil { + getParams.SetAccessKey(utils.StringValue(params.AccessKey)) + } + + aksk, err := akskCtl.Repo.Get(ctx, getParams) + if err != nil { + w.Error(err) + return + } + w.JSON(aksk) +} + +func (akskCtl AkSkController) DeleteAksk(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, params api.DeleteAkskParams) { + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return + } + + delParams := models.NewDeleteAkSkParams().SetUserID(operator.ID) + if params.Id != nil { + delParams.SetID(*params.Id) + } + + if params.AccessKey != nil { + delParams.SetAccessKey(utils.StringValue(params.AccessKey)) + } + + aksk, err := akskCtl.Repo.Delete(ctx, delParams) + if err != nil { + w.Error(err) + return + } + w.JSON(aksk) +} + +func (akskCtl AkSkController) ListAksks(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, params api.ListAksksParams) { + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return + } + + listParams := models.NewListAkSkParams().SetUserID(operator.ID) + if params.After != nil { + listParams.SetAfter(time.UnixMilli(utils.Int64Value(params.After))) + } + + if params.Amount != nil { + listParams.SetAmount(utils.IntValue(params.Amount)) + } + + aksks, hasMore, err := akskCtl.Repo.List(ctx, listParams) + if err != nil { + w.Error(err) + return + } + results := make([]api.Aksk, 0, len(aksks)) + for _, repo := range aksks { + results = append(results, *akskToDto(repo)) + } + pagMag := utils.PaginationFor(hasMore, results, "UpdatedAt") + pagination := api.Pagination{ + HasMore: pagMag.HasMore, + MaxPerPage: pagMag.MaxPerPage, + NextOffset: pagMag.NextOffset, + Results: pagMag.Results, + } + w.JSON(api.AkskList{ + Pagination: pagination, + Results: results, + }) +} + +func akskToDto(in *models.AkSk) *api.Aksk { + return &api.Aksk{ + AccessKey: in.AccessKey, + CreatedAt: in.CreatedAt.UnixMilli(), + Description: in.Description, + Id: in.ID, + SecretKey: in.SecretKey, + UpdatedAt: in.UpdatedAt.UnixMilli(), + } +} diff --git a/models/aksk.go b/models/aksk.go index 61859f40..00301b9e 100644 --- a/models/aksk.go +++ b/models/aksk.go @@ -25,6 +25,7 @@ type AkSk struct { } type GetAkSkParams struct { + userID uuid.UUID id uuid.UUID accessKey *string } @@ -37,7 +38,10 @@ func (gap *GetAkSkParams) SetID(id uuid.UUID) *GetAkSkParams { gap.id = id return gap } - +func (gap *GetAkSkParams) SetUserID(userID uuid.UUID) *GetAkSkParams { + gap.userID = userID + return gap +} func (gap *GetAkSkParams) SetAccessKey(ak string) *GetAkSkParams { gap.accessKey = &ak return gap @@ -69,6 +73,7 @@ func (lap *ListAkSkParams) SetAmount(amount int) *ListAkSkParams { } type DeleteAkSkParams struct { + userID uuid.UUID id uuid.UUID accessKey *string } @@ -81,6 +86,10 @@ func (dap *DeleteAkSkParams) SetID(id uuid.UUID) *DeleteAkSkParams { dap.id = id return dap } +func (dap *DeleteAkSkParams) SetUserID(userID uuid.UUID) *DeleteAkSkParams { + dap.userID = userID + return dap +} func (dap *DeleteAkSkParams) SetAccessKey(accessKey string) *DeleteAkSkParams { dap.accessKey = &accessKey @@ -121,6 +130,10 @@ func (a AkskRepo) Get(ctx context.Context, params *GetAkSkParams) (*AkSk, error) query = query.Where("id = ?", params.id) } + if uuid.Nil != params.userID { + query = query.Where("user_id = ?", params.userID) + } + if params.accessKey != nil { query = query.Where("access_key = ?", *params.accessKey) } @@ -156,6 +169,10 @@ func (a AkskRepo) Delete(ctx context.Context, params *DeleteAkSkParams) (int64, query = query.Where("id = ?", params.id) } + if uuid.Nil != params.userID { + query = query.Where("user_id = ?", params.userID) + } + if params.accessKey != nil { query = query.Where("access_key = ?", *params.accessKey) } diff --git a/models/aksk_test.go b/models/aksk_test.go index 802c7286..f0387c63 100644 --- a/models/aksk_test.go +++ b/models/aksk_test.go @@ -27,7 +27,7 @@ func TestAkskRepo_Delete(t *testing.T) { aksk, err := repo.Insert(ctx, akskModel) require.NoError(t, err) - expectAksk, err := repo.Get(ctx, models.NewGetAkSkParams().SetAccessKey(aksk.AccessKey).SetID(aksk.ID)) + expectAksk, err := repo.Get(ctx, models.NewGetAkSkParams().SetAccessKey(aksk.AccessKey).SetID(aksk.ID).SetUserID(aksk.UserID)) require.NoError(t, err) require.True(t, cmp.Equal(expectAksk, aksk, dbTimeCmpOpt)) }) @@ -68,7 +68,7 @@ func TestAkskRepo_Delete(t *testing.T) { aksk, err := repo.Insert(ctx, akskModel) require.NoError(t, err) - deleteRows, err := repo.Delete(ctx, models.NewDeleteAkSkParams().SetID(aksk.ID)) + deleteRows, err := repo.Delete(ctx, models.NewDeleteAkSkParams().SetUserID(aksk.UserID).SetID(aksk.ID)) require.NoError(t, err) require.Equal(t, int64(1), deleteRows) }) @@ -80,7 +80,7 @@ func TestAkskRepo_Delete(t *testing.T) { aksk, err := repo.Insert(ctx, akskModel) require.NoError(t, err) - deleteRows, err := repo.Delete(ctx, models.NewDeleteAkSkParams().SetAccessKey(aksk.AccessKey)) + deleteRows, err := repo.Delete(ctx, models.NewDeleteAkSkParams().SetUserID(aksk.UserID).SetAccessKey(aksk.AccessKey)) require.NoError(t, err) require.Equal(t, int64(1), deleteRows) }) From cf72ac52d59c87e344684b32d8cfd3a940c76983 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Fri, 23 Feb 2024 13:43:36 +0800 Subject: [PATCH 182/210] feat: support aksk verify --- api/api_impl/server.go | 13 +- api/jiaozifs.gen.go | 235 ++++++++++++++++++++++++------------- api/swagger.yml | 1 + auth/aksk.go | 26 ++++ auth/aksk/sign.go | 16 ++- auth/aksk/verifier.go | 44 ++++--- auth/aksk/verifier_test.go | 19 +-- auth/auth_middleware.go | 54 +++++++-- auth/authenticator.go | 39 ++++-- cmd/daemon.go | 7 ++ controller/aksk_ctl.go | 10 +- controller/user_ctl.go | 16 +-- 12 files changed, 327 insertions(+), 153 deletions(-) create mode 100644 auth/aksk.go diff --git a/api/api_impl/server.go b/api/api_impl/server.go index 01f40c10..db4c8a07 100644 --- a/api/api_impl/server.go +++ b/api/api_impl/server.go @@ -9,6 +9,8 @@ import ( "net/url" "strings" + "github.com/jiaozifs/jiaozifs/auth/aksk" + "github.com/hellofresh/health-go/v5" "github.com/getkin/kin-openapi/openapi3" @@ -37,7 +39,14 @@ const ( extensionValidationExcludeBody = "x-validation-exclude-body" ) -func SetupAPI(lc fx.Lifecycle, apiConfig *config.APIConfig, secretStore crypt.SecretStore, sessionStore sessions.Store, repo models.IRepo, controller APIController) error { +func SetupAPI(lc fx.Lifecycle, + authenticator *auth.BasicAuthenticator, + apiConfig *config.APIConfig, + secretStore crypt.SecretStore, + sessionStore sessions.Store, + repo models.IRepo, + verifier aksk.Verifier, + controller APIController) error { swagger, err := api.GetSwagger() if err != nil { return err @@ -66,7 +75,7 @@ func SetupAPI(lc fx.Lifecycle, apiConfig *config.APIConfig, secretStore crypt.Se OapiRequestValidatorWithOptions(swagger, &openapi3filter.Options{ AuthenticationFunc: openapi3filter.NoopAuthenticationFunc, }), - auth.Middleware(swagger, nil, secretStore, repo.UserRepo(), sessionStore), + auth.Middleware(swagger, authenticator, secretStore, repo.UserRepo(), repo.AkskRepo(), sessionStore, verifier), ) raw, err := api.RawSpec() diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 103a9983..2f611ad0 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -23,6 +23,7 @@ import ( ) const ( + Ak_skScopes = "ak_sk.Scopes" Basic_authScopes = "basic_auth.Scopes" Cookie_authScopes = "cookie_auth.Scopes" Jwt_tokenScopes = "jwt_token.Scopes" @@ -6862,6 +6863,8 @@ func (siw *ServerInterfaceWrapper) Logout(w http.ResponseWriter, r *http.Request ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.Logout(r.Context(), &JiaozifsResponse{w}, r) })) @@ -6912,6 +6915,8 @@ func (siw *ServerInterfaceWrapper) GetMergeRequest(w http.ResponseWriter, r *htt ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetMergeRequest(r.Context(), &JiaozifsResponse{w}, r, owner, repository, mrSeq) })) @@ -6972,6 +6977,8 @@ func (siw *ServerInterfaceWrapper) UpdateMergeRequest(w http.ResponseWriter, r * ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.UpdateMergeRequest(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository, mrSeq) })) @@ -7013,6 +7020,8 @@ func (siw *ServerInterfaceWrapper) ListMergeRequests(w http.ResponseWriter, r *h ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params ListMergeRequestsParams @@ -7091,6 +7100,8 @@ func (siw *ServerInterfaceWrapper) CreateMergeRequest(w http.ResponseWriter, r * ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.CreateMergeRequest(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository) })) @@ -7151,6 +7162,8 @@ func (siw *ServerInterfaceWrapper) Merge(w http.ResponseWriter, r *http.Request) ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.Merge(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository, mrSeq) })) @@ -7192,6 +7205,8 @@ func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.R ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params DeleteObjectParams @@ -7266,6 +7281,8 @@ func (siw *ServerInterfaceWrapper) GetObject(w http.ResponseWriter, r *http.Requ ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetObjectParams @@ -7376,6 +7393,8 @@ func (siw *ServerInterfaceWrapper) HeadObject(w http.ResponseWriter, r *http.Req ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params HeadObjectParams @@ -7486,6 +7505,8 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params UploadObjectParams @@ -7560,6 +7581,8 @@ func (siw *ServerInterfaceWrapper) DeleteRepository(w http.ResponseWriter, r *ht ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params DeleteRepositoryParams @@ -7612,6 +7635,8 @@ func (siw *ServerInterfaceWrapper) GetRepository(w http.ResponseWriter, r *http. ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetRepository(r.Context(), &JiaozifsResponse{w}, r, owner, repository) })) @@ -7663,6 +7688,8 @@ func (siw *ServerInterfaceWrapper) UpdateRepository(w http.ResponseWriter, r *ht ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.UpdateRepository(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository) })) @@ -7704,6 +7731,8 @@ func (siw *ServerInterfaceWrapper) DeleteBranch(w http.ResponseWriter, r *http.R ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params DeleteBranchParams @@ -7763,6 +7792,8 @@ func (siw *ServerInterfaceWrapper) GetBranch(w http.ResponseWriter, r *http.Requ ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetBranchParams @@ -7832,6 +7863,8 @@ func (siw *ServerInterfaceWrapper) CreateBranch(w http.ResponseWriter, r *http.R ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.CreateBranch(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository) })) @@ -7873,6 +7906,8 @@ func (siw *ServerInterfaceWrapper) ListBranches(w http.ResponseWriter, r *http.R ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params ListBranchesParams @@ -7950,6 +7985,8 @@ func (siw *ServerInterfaceWrapper) GetCommitChanges(w http.ResponseWriter, r *ht ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetCommitChangesParams @@ -8002,6 +8039,8 @@ func (siw *ServerInterfaceWrapper) GetCommitsInRef(w http.ResponseWriter, r *htt ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetCommitsInRefParams @@ -8079,6 +8118,8 @@ func (siw *ServerInterfaceWrapper) CompareCommit(w http.ResponseWriter, r *http. ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params CompareCommitParams @@ -8131,6 +8172,8 @@ func (siw *ServerInterfaceWrapper) GetEntriesInRef(w http.ResponseWriter, r *htt ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetEntriesInRefParams @@ -8203,6 +8246,8 @@ func (siw *ServerInterfaceWrapper) DeleteAksk(w http.ResponseWriter, r *http.Req ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params DeleteAkskParams @@ -8245,6 +8290,8 @@ func (siw *ServerInterfaceWrapper) GetAksk(w http.ResponseWriter, r *http.Reques ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetAkskParams @@ -8287,6 +8334,8 @@ func (siw *ServerInterfaceWrapper) CreateAksk(w http.ResponseWriter, r *http.Req ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params CreateAkskParams @@ -8321,6 +8370,8 @@ func (siw *ServerInterfaceWrapper) ListAksks(w http.ResponseWriter, r *http.Requ ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params ListAksksParams @@ -8407,6 +8458,8 @@ func (siw *ServerInterfaceWrapper) ListRepositoryOfAuthenticatedUser(w http.Resp ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params ListRepositoryOfAuthenticatedUserParams @@ -8465,6 +8518,8 @@ func (siw *ServerInterfaceWrapper) CreateRepository(w http.ResponseWriter, r *ht ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.CreateRepository(r.Context(), &JiaozifsResponse{w}, r, body) })) @@ -8486,6 +8541,8 @@ func (siw *ServerInterfaceWrapper) GetUserInfo(w http.ResponseWriter, r *http.Re ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetUserInfo(r.Context(), &JiaozifsResponse{w}, r) })) @@ -8518,6 +8575,8 @@ func (siw *ServerInterfaceWrapper) ListRepository(w http.ResponseWriter, r *http ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params ListRepositoryParams @@ -8601,6 +8660,8 @@ func (siw *ServerInterfaceWrapper) DeleteWip(w http.ResponseWriter, r *http.Requ ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params DeleteWipParams @@ -8660,6 +8721,8 @@ func (siw *ServerInterfaceWrapper) GetWip(w http.ResponseWriter, r *http.Request ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetWipParams @@ -8729,6 +8792,8 @@ func (siw *ServerInterfaceWrapper) UpdateWip(w http.ResponseWriter, r *http.Requ ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params UpdateWipParams @@ -8788,6 +8853,8 @@ func (siw *ServerInterfaceWrapper) GetWipChanges(w http.ResponseWriter, r *http. ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetWipChangesParams @@ -8855,6 +8922,8 @@ func (siw *ServerInterfaceWrapper) CommitWip(w http.ResponseWriter, r *http.Requ ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params CommitWipParams @@ -8929,6 +8998,8 @@ func (siw *ServerInterfaceWrapper) ListWip(w http.ResponseWriter, r *http.Reques ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.ListWip(r.Context(), &JiaozifsResponse{w}, r, owner, repository) })) @@ -8970,6 +9041,8 @@ func (siw *ServerInterfaceWrapper) RevertWipChanges(w http.ResponseWriter, r *ht ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params RevertWipChangesParams @@ -9255,90 +9328,90 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 7681ad4c..3ddcd25e 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -17,6 +17,7 @@ security: - jwt_token: [] # Default security for the entire API - basic_auth: [] - cookie_auth: [] + - ak_sk: [] components: parameters: PaginationPrefix: diff --git a/auth/aksk.go b/auth/aksk.go new file mode 100644 index 00000000..4888516a --- /dev/null +++ b/auth/aksk.go @@ -0,0 +1,26 @@ +package auth + +import ( + "context" + + "github.com/jiaozifs/jiaozifs/auth/aksk" + "github.com/jiaozifs/jiaozifs/models" +) + +var _ aksk.SkGetter = (*SkGetter)(nil) + +type SkGetter struct { + akskRepo models.IAkskRepo +} + +func (s SkGetter) Get(ak string) (string, error) { + aksk, err := s.akskRepo.Get(context.Background(), models.NewGetAkSkParams().SetAccessKey(ak)) + if err != nil { + return "", err + } + return aksk.SecretKey, nil +} + +func NewAkskVerifier(repo models.IRepo) aksk.Verifier { + return aksk.NewV0Verier(SkGetter{repo.AkskRepo()}) +} diff --git a/auth/aksk/sign.go b/auth/aksk/sign.go index 9ffcdec7..ca459b08 100644 --- a/auth/aksk/sign.go +++ b/auth/aksk/sign.go @@ -12,6 +12,12 @@ import ( ) const ( + AccessKeykey = "JiaozifsAccessKeyId" + SignatureVersionKey = "SignatureVersion" + SignatureMethodKey = "SignatureMethod" + TimestampKey = "Timestamp" + SignatureKey = "Signature" + signatureVersion = "0" signatureMethod = "HmacSHA256" timeFormat = "2006-01-02T15:04:05Z" @@ -37,10 +43,10 @@ func (voSigner V0Signer) Sign(req *http.Request) error { curTime := time.Now() // set query parameter query := req.URL.Query() - query.Set("AWSAccessKeyId", voSigner.accessKey) - query.Set("SignatureVersion", signatureVersion) - query.Set("SignatureMethod", signatureMethod) - query.Set("Timestamp", curTime.UTC().Format(timeFormat)) + query.Set(AccessKeykey, voSigner.accessKey) + query.Set(SignatureVersionKey, signatureVersion) + query.Set(SignatureVersionKey, signatureMethod) + query.Set(TimestampKey, curTime.UTC().Format(timeFormat)) req.Header.Del("Signature") @@ -80,7 +86,7 @@ func (voSigner V0Signer) Sign(req *http.Request) error { hash := hmac.New(sha256.New, []byte(voSigner.secretKey)) hash.Write([]byte(stringToSign)) signature := base64.StdEncoding.EncodeToString(hash.Sum(nil)) - query.Set("Signature", signature) + query.Set(SignatureKey, signature) req.URL.RawQuery = query.Encode() return nil diff --git a/auth/aksk/verifier.go b/auth/aksk/verifier.go index 792cd8d6..39474a2d 100644 --- a/auth/aksk/verifier.go +++ b/auth/aksk/verifier.go @@ -12,15 +12,18 @@ import ( "time" ) -type V0Verifier interface { - Verify(req *http.Request) error +type Verifier interface { + // IsAkskCredential check the requess is aksk credential, just check request have AccessKeykey + IsAkskCredential(req *http.Request) bool + // Verify verify the request and return access key + Verify(req *http.Request) (string, error) } type SkGetter interface { Get(ak string) (string, error) } -var _ V0Verifier = (*V0Verier)(nil) +var _ Verifier = (*V0Verier)(nil) type V0Verier struct { skGetter SkGetter @@ -30,38 +33,43 @@ func NewV0Verier(skGetter SkGetter) *V0Verier { return &V0Verier{skGetter: skGetter} } -func (v *V0Verier) Verify(req *http.Request) error { +func (v *V0Verier) IsAkskCredential(req *http.Request) bool { + accessKey := req.URL.Query().Get(AccessKeykey) + return len(accessKey) > 0 +} + +func (v *V0Verier) Verify(req *http.Request) (string, error) { query := req.URL.Query() - accessKey := query.Get("AWSAccessKeyId") + accessKey := query.Get(AccessKeykey) if len(accessKey) == 0 { - return fmt.Errorf("ak not found") + return "", fmt.Errorf("ak not found") } secretKey, err := v.skGetter.Get(accessKey) if err != nil { - return fmt.Errorf("access key not correct") + return "", fmt.Errorf("access key not correct") } - sigMethod := query.Get("SignatureMethod") + sigMethod := query.Get(SignatureMethodKey) if sigMethod != signatureMethod { - return fmt.Errorf("invalid signature method %s", sigMethod) + return "", fmt.Errorf("invalid signature method %s", sigMethod) } - sigVersion := query.Get("SignatureVersion") + sigVersion := query.Get(SignatureVersionKey) if sigVersion != signatureVersion { - return fmt.Errorf("invalid signature method %s", sigMethod) + return "", fmt.Errorf("invalid signature method %s", sigMethod) } - reqTime := query.Get("Timestamp") + reqTime := query.Get(TimestampKey) t, err := time.Parse(timeFormat, reqTime) if err != nil { - return fmt.Errorf("invalid timestamp %s", reqTime) + return "", fmt.Errorf("invalid timestamp %s", reqTime) } if t.Before(time.Now().Add(-5 * time.Minute)) { - return fmt.Errorf("request is out of data") + return "", fmt.Errorf("request is out of data") } - expectSignature := query.Get("Signature") - query.Del("Signature") + expectSignature := query.Get(SignatureKey) + query.Del(SignatureKey) method := req.Method host := req.URL.Host @@ -99,7 +107,7 @@ func (v *V0Verier) Verify(req *http.Request) error { hash.Write([]byte(stringToSign)) actualSig := base64.StdEncoding.EncodeToString(hash.Sum(nil)) if actualSig != expectSignature { - return fmt.Errorf("signature not correct") + return "", fmt.Errorf("signature not correct") } - return nil + return accessKey, nil } diff --git a/auth/aksk/verifier_test.go b/auth/aksk/verifier_test.go index 54749811..c4e7044e 100644 --- a/auth/aksk/verifier_test.go +++ b/auth/aksk/verifier_test.go @@ -24,8 +24,9 @@ func TestFull(t *testing.T) { err = signer.Sign(req) require.NoError(t, err) - err = verifer.Verify(req) + actualAk, err := verifer.Verify(req) require.NoError(t, err) + require.Equal(t, ak, actualAk) }) t.Run("fail verify", func(t *testing.T) { @@ -39,7 +40,7 @@ func TestFull(t *testing.T) { query := req.URL.Query() query.Add("a", "b") req.URL.RawQuery = query.Encode() - err = verifer.Verify(req) + _, err = verifer.Verify(req) require.Error(t, err) }) t.Run("no access id", func(t *testing.T) { @@ -53,7 +54,7 @@ func TestFull(t *testing.T) { query := req.URL.Query() query.Del("AWSAccessKeyId") req.URL.RawQuery = query.Encode() - err = verifer.Verify(req) + _, err = verifer.Verify(req) require.Error(t, err) }) t.Run("sig method fail", func(t *testing.T) { @@ -67,7 +68,7 @@ func TestFull(t *testing.T) { query := req.URL.Query() query.Set("SignatureMethod", "2") req.URL.RawQuery = query.Encode() - err = verifer.Verify(req) + _, err = verifer.Verify(req) require.Error(t, err) }) t.Run("sig method fail", func(t *testing.T) { @@ -81,7 +82,7 @@ func TestFull(t *testing.T) { query := req.URL.Query() query.Set("SignatureMethod", "md5") req.URL.RawQuery = query.Encode() - err = verifer.Verify(req) + _, err = verifer.Verify(req) require.Error(t, err) }) t.Run("sig version fail", func(t *testing.T) { @@ -95,7 +96,7 @@ func TestFull(t *testing.T) { query := req.URL.Query() query.Set("SignatureVersion", "2") req.URL.RawQuery = query.Encode() - err = verifer.Verify(req) + _, err = verifer.Verify(req) require.Error(t, err) }) t.Run("no timestamp", func(t *testing.T) { @@ -109,7 +110,7 @@ func TestFull(t *testing.T) { query := req.URL.Query() query.Del("Timestamp") req.URL.RawQuery = query.Encode() - err = verifer.Verify(req) + _, err = verifer.Verify(req) require.Error(t, err) }) @@ -124,7 +125,7 @@ func TestFull(t *testing.T) { query := req.URL.Query() query.Set("Timestamp", time.Now().String()) req.URL.RawQuery = query.Encode() - err = verifer.Verify(req) + _, err = verifer.Verify(req) require.Error(t, err) }) t.Run("request out of date", func(t *testing.T) { @@ -138,7 +139,7 @@ func TestFull(t *testing.T) { query := req.URL.Query() query.Set("Timestamp", time.Now().Add(-time.Minute*10).UTC().Format(timeFormat)) req.URL.RawQuery = query.Encode() - err = verifer.Verify(req) + _, err = verifer.Verify(req) require.Error(t, err) }) } diff --git a/auth/auth_middleware.go b/auth/auth_middleware.go index 2280f3ad..85f04e05 100644 --- a/auth/auth_middleware.go +++ b/auth/auth_middleware.go @@ -7,6 +7,8 @@ import ( "net/http" "strings" + "github.com/jiaozifs/jiaozifs/auth/aksk" + "github.com/golang-jwt/jwt/v5" "github.com/getkin/kin-openapi/openapi3" @@ -53,11 +55,19 @@ type CookieAuthConfig struct { AuthSource string } -func Middleware(swagger *openapi3.T, authenticator Authenticator, secretStore crypt.SecretStore, userRepo models.IUserRepo, sessionStore sessions.Store) func(next http.Handler) http.Handler { +func Middleware(swagger *openapi3.T, + authenticator *BasicAuthenticator, + secretStore crypt.SecretStore, + userRepo models.IUserRepo, + akskRepo models.IAkskRepo, + sessionStore sessions.Store, + verifier aksk.Verifier, +) func(next http.Handler) http.Handler { router, err := legacy.NewRouter(swagger) if err != nil { panic(err) } + return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // if request already authenticated @@ -72,7 +82,7 @@ func Middleware(swagger *openapi3.T, authenticator Authenticator, secretStore cr _, _ = w.Write([]byte(err.Error())) return } - user, err := checkSecurityRequirements(r, securityRequirements, authenticator, sessionStore, secretStore, userRepo) + user, err := checkSecurityRequirements(r, securityRequirements, authenticator, sessionStore, secretStore, verifier, userRepo, akskRepo) if err != nil { w.WriteHeader(http.StatusUnauthorized) _, _ = w.Write([]byte(err.Error())) @@ -90,10 +100,12 @@ func Middleware(swagger *openapi3.T, authenticator Authenticator, secretStore cr // it will return nil user and error in case of no security checks to match. func checkSecurityRequirements(r *http.Request, securityRequirements openapi3.SecurityRequirements, - authenticator Authenticator, + authenticator *BasicAuthenticator, sessionStore sessions.Store, secretStore crypt.SecretStore, + verifier aksk.Verifier, userRepo models.IUserRepo, + akskRepo models.IAkskRepo, ) (*models.User, error) { ctx := r.Context() var user *models.User @@ -120,7 +132,8 @@ func checkSecurityRequirements(r *http.Request, if !ok { continue } - user, err = userByAuth(ctx, authenticator, userRepo, userName, password) + + user, err = userByAuth(ctx, authenticator, userName, password) case "cookie_auth": var internalAuthSession *sessions.Session internalAuthSession, _ = sessionStore.Get(r, InternalAuthSessionName) @@ -132,6 +145,12 @@ func checkSecurityRequirements(r *http.Request, continue } user, err = userByToken(ctx, userRepo, secretStore.SharedSecret(), token) + case "ak_sk": + isAkskRequest := verifier.IsAkskCredential(r) + if !isAkskRequest { + continue + } + user, err = userByAKSK(ctx, akskRepo, userRepo, verifier, r) default: // unknown security requirement to check log.With("provider", provider).Error("Authentication middleware unknown security requirement provider") @@ -149,6 +168,24 @@ func checkSecurityRequirements(r *http.Request, return nil, nil } +func userByAKSK(ctx context.Context, akskRepo models.IAkskRepo, userRepo models.IUserRepo, verifier aksk.Verifier, r *http.Request) (*models.User, error) { + ak, err := verifier.Verify(r) + if err != nil { + return nil, err + } + + akModel, err := akskRepo.Get(ctx, models.NewGetAkSkParams().SetAccessKey(ak)) + if err != nil { + return nil, err + } + + userModel, err := userRepo.Get(ctx, models.NewGetUserParams().SetID(akModel.UserID)) + if err != nil { + return nil, err + } + return userModel, nil +} + func userByToken(ctx context.Context, userRepo models.IUserRepo, secret []byte, tokenString string) (*models.User, error) { claims, err := VerifyToken(secret, tokenString) if err != nil { @@ -177,16 +214,11 @@ func userByToken(ctx context.Context, userRepo models.IUserRepo, secret []byte, return userData, nil } -func userByAuth(ctx context.Context, authenticator Authenticator, userRepo models.IUserRepo, accessKey string, secretKey string) (*models.User, error) { - username, err := authenticator.AuthenticateUser(ctx, accessKey, secretKey) +func userByAuth(ctx context.Context, authenticator *BasicAuthenticator, accessKey string, secretKey string) (*models.User, error) { + user, err := authenticator.AuthenticateUser(ctx, accessKey, secretKey) if err != nil { log.With("user", accessKey).Errorf("authenticate %v", err) return nil, ErrAuthenticatingRequest } - user, err := userRepo.Get(ctx, models.NewGetUserParams().SetName(username)) - if err != nil { - log.With("user_name", username).Debugf("could not find user id by credentials %s", err) - return nil, ErrAuthenticatingRequest - } return user, nil } diff --git a/auth/authenticator.go b/auth/authenticator.go index 996865a8..5248daa2 100644 --- a/auth/authenticator.go +++ b/auth/authenticator.go @@ -1,13 +1,32 @@ package auth -import "context" - -// Authenticator authenticates users returning an identifier for the user. -// (Currently it handles only username+password single-step authentication. -// This interface will need to change significantly in order to support -// challenge-response protocols.) -type Authenticator interface { - // AuthenticateUser authenticates a user matching username and - // password and returns their ID. - AuthenticateUser(ctx context.Context, ak, sk string) (string, error) +import ( + "context" + + "github.com/jiaozifs/jiaozifs/models" + "golang.org/x/crypto/bcrypt" +) + +type BasicAuthenticator struct { + userRepo models.IUserRepo +} + +func NewBasicAuthenticator(userRepo models.IUserRepo) *BasicAuthenticator { + return &BasicAuthenticator{userRepo: userRepo} +} + +func (b BasicAuthenticator) AuthenticateUser(ctx context.Context, user, password string) (*models.User, error) { + // get user encryptedPassword by username + ep, err := b.userRepo.GetEPByName(ctx, user) + if err != nil { + return nil, err + } + + // Compare ep and password + err = bcrypt.CompareHashAndPassword([]byte(ep), []byte(password)) + if err != nil { + return nil, err + } + + return b.userRepo.Get(ctx, models.NewGetUserParams().SetName(user)) } diff --git a/cmd/daemon.go b/cmd/daemon.go index 5523cb02..944c1129 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -3,6 +3,8 @@ package cmd import ( "context" + "github.com/jiaozifs/jiaozifs/auth/aksk" + "github.com/pelletier/go-toml/v2" "github.com/gorilla/sessions" @@ -62,11 +64,16 @@ var daemonCmd = &cobra.Command{ fx_opt.Override(new(models.IRepo), func(db *bun.DB) models.IRepo { return models.NewRepo(db) }), + fx_opt.Override(new(models.IUserRepo), func(repo models.IRepo) models.IUserRepo { + return repo.UserRepo() + }), fx_opt.Override(fx_opt.NextInvoke(), migrations.MigrateDatabase), //api fx_opt.Override(new(crypt.SecretStore), auth.NewSectetStore), fx_opt.Override(new(sessions.Store), auth.NewSessionStore), + fx_opt.Override(new(*auth.BasicAuthenticator), auth.NewBasicAuthenticator), + fx_opt.Override(new(aksk.Verifier), auth.NewAkskVerifier), fx_opt.Override(fx_opt.NextInvoke(), apiImpl.SetupAPI), ) if err != nil { diff --git a/controller/aksk_ctl.go b/controller/aksk_ctl.go index 03f23f69..108ad71f 100644 --- a/controller/aksk_ctl.go +++ b/controller/aksk_ctl.go @@ -19,7 +19,7 @@ import ( type AkSkController struct { fx.In - Repo models.IAkskRepo + Repo models.IRepo } func (akskCtl AkSkController) CreateAksk(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, params api.CreateAkskParams) { @@ -43,7 +43,7 @@ func (akskCtl AkSkController) CreateAksk(ctx context.Context, w *api.JiaozifsRes CreatedAt: time.Now(), UpdatedAt: time.Now(), } - aksk, err = akskCtl.Repo.Insert(ctx, aksk) + aksk, err = akskCtl.Repo.AkskRepo().Insert(ctx, aksk) if err != nil { w.Error(err) return @@ -67,7 +67,7 @@ func (akskCtl AkSkController) GetAksk(ctx context.Context, w *api.JiaozifsRespon getParams.SetAccessKey(utils.StringValue(params.AccessKey)) } - aksk, err := akskCtl.Repo.Get(ctx, getParams) + aksk, err := akskCtl.Repo.AkskRepo().Get(ctx, getParams) if err != nil { w.Error(err) return @@ -91,7 +91,7 @@ func (akskCtl AkSkController) DeleteAksk(ctx context.Context, w *api.JiaozifsRes delParams.SetAccessKey(utils.StringValue(params.AccessKey)) } - aksk, err := akskCtl.Repo.Delete(ctx, delParams) + aksk, err := akskCtl.Repo.AkskRepo().Delete(ctx, delParams) if err != nil { w.Error(err) return @@ -115,7 +115,7 @@ func (akskCtl AkSkController) ListAksks(ctx context.Context, w *api.JiaozifsResp listParams.SetAmount(utils.IntValue(params.Amount)) } - aksks, hasMore, err := akskCtl.Repo.List(ctx, listParams) + aksks, hasMore, err := akskCtl.Repo.AkskRepo().List(ctx, listParams) if err != nil { w.Error(err) return diff --git a/controller/user_ctl.go b/controller/user_ctl.go index 66b96fb7..47abaf1a 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -19,7 +19,6 @@ import ( "github.com/jiaozifs/jiaozifs/models" openapitypes "github.com/oapi-codegen/runtime/types" "go.uber.org/fx" - "golang.org/x/crypto/bcrypt" ) var userCtlLog = logging.Logger("user_ctl") @@ -34,24 +33,17 @@ type UserController struct { SessionStore sessions.Store Repo models.IRepo Config *config.AuthConfig + + BasicAuthenticator *auth.BasicAuthenticator } func (userCtl UserController) Login(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, body api.LoginJSONRequestBody) { - // get user encryptedPassword by username - ep, err := userCtl.Repo.UserRepo().GetEPByName(ctx, body.Name) + user, err := userCtl.BasicAuthenticator.AuthenticateUser(ctx, body.Name, body.Password) if err != nil { w.Code(http.StatusUnauthorized) return } - - // Compare ep and password - err = bcrypt.CompareHashAndPassword([]byte(ep), []byte(body.Password)) - if err != nil { - w.Code(http.StatusUnauthorized) - return - } - - userCtl.generateAndRespToken(w, r, body.Name) + userCtl.generateAndRespToken(w, r, user.Name) } func (userCtl UserController) RefreshToken(ctx context.Context, w *api.JiaozifsResponse, r *http.Request) { From 570280d90927c3f4672f41b7169e4b718ebe9ddd Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Fri, 23 Feb 2024 15:25:53 +0800 Subject: [PATCH 183/210] test: integrate test --- api/aksk_opts.go | 18 ++ api/jiaozifs.gen.go | 540 +++++++++++++++++++++++++++-------- api/swagger.yml | 29 +- auth/aksk/verifier_test.go | 4 +- controller/aksk_ctl.go | 16 +- controller/user_ctl.go | 2 +- integrationtest/aksk_test.go | 158 ++++++++++ integrationtest/root_test.go | 5 +- integrationtest/user_test.go | 2 +- makefile | 2 +- 10 files changed, 632 insertions(+), 144 deletions(-) create mode 100644 api/aksk_opts.go create mode 100644 integrationtest/aksk_test.go diff --git a/api/aksk_opts.go b/api/aksk_opts.go new file mode 100644 index 00000000..d1dc41e7 --- /dev/null +++ b/api/aksk_opts.go @@ -0,0 +1,18 @@ +package api + +import ( + "context" + "net/http" + + "github.com/jiaozifs/jiaozifs/auth/aksk" +) + +func AkSkOption(ak, sk string) ClientOption { + return func(client *Client) error { + client.RequestEditors = append(client.RequestEditors, func(_ context.Context, req *http.Request) error { + signer := aksk.NewV0Signer(ak, sk) + return signer.Sign(req) + }) + return nil + } +} diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 2f611ad0..c7af4ed9 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -23,10 +23,14 @@ import ( ) const ( - Ak_skScopes = "ak_sk.Scopes" - Basic_authScopes = "basic_auth.Scopes" - Cookie_authScopes = "cookie_auth.Scopes" - Jwt_tokenScopes = "jwt_token.Scopes" + Access_key_idScopes = "access_key_id.Scopes" + Basic_authScopes = "basic_auth.Scopes" + Cookie_authScopes = "cookie_auth.Scopes" + Jwt_tokenScopes = "jwt_token.Scopes" + SignatureScopes = "signature.Scopes" + Signature_methodScopes = "signature_method.Scopes" + Signature_versionScopes = "signature_version.Scopes" + TimestampScopes = "timestamp.Scopes" ) // Defines values for ChangeAction. @@ -6863,7 +6867,15 @@ func (siw *ServerInterfaceWrapper) Logout(w http.ResponseWriter, r *http.Request ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.Logout(r.Context(), &JiaozifsResponse{w}, r) @@ -6915,7 +6927,15 @@ func (siw *ServerInterfaceWrapper) GetMergeRequest(w http.ResponseWriter, r *htt ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetMergeRequest(r.Context(), &JiaozifsResponse{w}, r, owner, repository, mrSeq) @@ -6977,7 +6997,15 @@ func (siw *ServerInterfaceWrapper) UpdateMergeRequest(w http.ResponseWriter, r * ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.UpdateMergeRequest(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository, mrSeq) @@ -7020,7 +7048,15 @@ func (siw *ServerInterfaceWrapper) ListMergeRequests(w http.ResponseWriter, r *h ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params ListMergeRequestsParams @@ -7100,7 +7136,15 @@ func (siw *ServerInterfaceWrapper) CreateMergeRequest(w http.ResponseWriter, r * ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.CreateMergeRequest(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository) @@ -7162,7 +7206,15 @@ func (siw *ServerInterfaceWrapper) Merge(w http.ResponseWriter, r *http.Request) ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.Merge(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository, mrSeq) @@ -7205,7 +7257,15 @@ func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.R ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params DeleteObjectParams @@ -7281,7 +7341,15 @@ func (siw *ServerInterfaceWrapper) GetObject(w http.ResponseWriter, r *http.Requ ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params GetObjectParams @@ -7393,7 +7461,15 @@ func (siw *ServerInterfaceWrapper) HeadObject(w http.ResponseWriter, r *http.Req ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params HeadObjectParams @@ -7505,7 +7581,15 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params UploadObjectParams @@ -7581,7 +7665,15 @@ func (siw *ServerInterfaceWrapper) DeleteRepository(w http.ResponseWriter, r *ht ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params DeleteRepositoryParams @@ -7635,7 +7727,15 @@ func (siw *ServerInterfaceWrapper) GetRepository(w http.ResponseWriter, r *http. ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetRepository(r.Context(), &JiaozifsResponse{w}, r, owner, repository) @@ -7688,7 +7788,15 @@ func (siw *ServerInterfaceWrapper) UpdateRepository(w http.ResponseWriter, r *ht ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.UpdateRepository(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository) @@ -7731,7 +7839,15 @@ func (siw *ServerInterfaceWrapper) DeleteBranch(w http.ResponseWriter, r *http.R ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params DeleteBranchParams @@ -7792,7 +7908,15 @@ func (siw *ServerInterfaceWrapper) GetBranch(w http.ResponseWriter, r *http.Requ ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params GetBranchParams @@ -7863,7 +7987,15 @@ func (siw *ServerInterfaceWrapper) CreateBranch(w http.ResponseWriter, r *http.R ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.CreateBranch(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository) @@ -7906,7 +8038,15 @@ func (siw *ServerInterfaceWrapper) ListBranches(w http.ResponseWriter, r *http.R ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params ListBranchesParams @@ -7985,7 +8125,15 @@ func (siw *ServerInterfaceWrapper) GetCommitChanges(w http.ResponseWriter, r *ht ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params GetCommitChangesParams @@ -8039,7 +8187,15 @@ func (siw *ServerInterfaceWrapper) GetCommitsInRef(w http.ResponseWriter, r *htt ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params GetCommitsInRefParams @@ -8118,7 +8274,15 @@ func (siw *ServerInterfaceWrapper) CompareCommit(w http.ResponseWriter, r *http. ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params CompareCommitParams @@ -8172,7 +8336,15 @@ func (siw *ServerInterfaceWrapper) GetEntriesInRef(w http.ResponseWriter, r *htt ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params GetEntriesInRefParams @@ -8246,7 +8418,15 @@ func (siw *ServerInterfaceWrapper) DeleteAksk(w http.ResponseWriter, r *http.Req ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params DeleteAkskParams @@ -8290,7 +8470,15 @@ func (siw *ServerInterfaceWrapper) GetAksk(w http.ResponseWriter, r *http.Reques ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params GetAkskParams @@ -8334,7 +8522,15 @@ func (siw *ServerInterfaceWrapper) CreateAksk(w http.ResponseWriter, r *http.Req ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params CreateAkskParams @@ -8370,7 +8566,15 @@ func (siw *ServerInterfaceWrapper) ListAksks(w http.ResponseWriter, r *http.Requ ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params ListAksksParams @@ -8458,7 +8662,15 @@ func (siw *ServerInterfaceWrapper) ListRepositoryOfAuthenticatedUser(w http.Resp ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params ListRepositoryOfAuthenticatedUserParams @@ -8518,7 +8730,15 @@ func (siw *ServerInterfaceWrapper) CreateRepository(w http.ResponseWriter, r *ht ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.CreateRepository(r.Context(), &JiaozifsResponse{w}, r, body) @@ -8541,7 +8761,15 @@ func (siw *ServerInterfaceWrapper) GetUserInfo(w http.ResponseWriter, r *http.Re ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetUserInfo(r.Context(), &JiaozifsResponse{w}, r) @@ -8575,7 +8803,15 @@ func (siw *ServerInterfaceWrapper) ListRepository(w http.ResponseWriter, r *http ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params ListRepositoryParams @@ -8660,7 +8896,15 @@ func (siw *ServerInterfaceWrapper) DeleteWip(w http.ResponseWriter, r *http.Requ ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params DeleteWipParams @@ -8721,7 +8965,15 @@ func (siw *ServerInterfaceWrapper) GetWip(w http.ResponseWriter, r *http.Request ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params GetWipParams @@ -8792,7 +9044,15 @@ func (siw *ServerInterfaceWrapper) UpdateWip(w http.ResponseWriter, r *http.Requ ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params UpdateWipParams @@ -8853,7 +9113,15 @@ func (siw *ServerInterfaceWrapper) GetWipChanges(w http.ResponseWriter, r *http. ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params GetWipChangesParams @@ -8922,7 +9190,15 @@ func (siw *ServerInterfaceWrapper) CommitWip(w http.ResponseWriter, r *http.Requ ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params CommitWipParams @@ -8998,7 +9274,15 @@ func (siw *ServerInterfaceWrapper) ListWip(w http.ResponseWriter, r *http.Reques ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.ListWip(r.Context(), &JiaozifsResponse{w}, r, owner, repository) @@ -9041,7 +9325,15 @@ func (siw *ServerInterfaceWrapper) RevertWipChanges(w http.ResponseWriter, r *ht ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Ak_skScopes, []string{}) + ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + + ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params RevertWipChangesParams @@ -9328,90 +9620,92 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "AP//KxKoD8WUAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 3ddcd25e..780c28de 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -17,7 +17,11 @@ security: - jwt_token: [] # Default security for the entire API - basic_auth: [] - cookie_auth: [] - - ak_sk: [] + - access_key_id: [] + signature_version: [] + signature_method: [] + timestamp: [] + signature: [] components: parameters: PaginationPrefix: @@ -72,7 +76,26 @@ components: type: apiKey in: cookie name: internal_auth_session - + access_key_id: + type: apiKey + in: query + name: JiaozifsAccessKeyId + signature_version: + type: apiKey + in: query + name: SignatureVersion + signature_method: + type: apiKey + in: query + name: SignatureMethod + timestamp: + type: apiKey + in: query + name: Timestamp + signature: + type: apiKey + in: query + name: Signature schemas: LoginConfig: @@ -2155,8 +2178,6 @@ paths: $ref: "#/components/schemas/UserInfo" 401: description: Unauthorized - 403: - description: Forbiden default: description: Internal Server Error diff --git a/auth/aksk/verifier_test.go b/auth/aksk/verifier_test.go index c4e7044e..13b0005d 100644 --- a/auth/aksk/verifier_test.go +++ b/auth/aksk/verifier_test.go @@ -148,11 +148,11 @@ type skGetter struct { sk string } -func (getter skGetter) Get(ak string) (string, error) { +func (getter skGetter) Get(_ string) (string, error) { return getter.sk, nil } -func mockHttpRequest() *http.Request { +func mockHttpRequest() *http.Request { //nolint verbs := []string{"GET", "POST", "PUT", "Delete"} req, _ := http.NewRequest(verbs[rand.Intn(3)], "http://www.xx.com/index.html", closerWraper{io.LimitReader(crand.Reader, 100)}) diff --git a/controller/aksk_ctl.go b/controller/aksk_ctl.go index 108ad71f..6e65133e 100644 --- a/controller/aksk_ctl.go +++ b/controller/aksk_ctl.go @@ -22,7 +22,7 @@ type AkSkController struct { Repo models.IRepo } -func (akskCtl AkSkController) CreateAksk(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, params api.CreateAkskParams) { +func (akskCtl AkSkController) CreateAksk(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, params api.CreateAkskParams) { operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) @@ -48,10 +48,10 @@ func (akskCtl AkSkController) CreateAksk(ctx context.Context, w *api.JiaozifsRes w.Error(err) return } - w.JSON(aksk, http.StatusCreated) + w.JSON(akskToDto(aksk), http.StatusCreated) } -func (akskCtl AkSkController) GetAksk(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, params api.GetAkskParams) { +func (akskCtl AkSkController) GetAksk(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, params api.GetAkskParams) { operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) @@ -72,10 +72,10 @@ func (akskCtl AkSkController) GetAksk(ctx context.Context, w *api.JiaozifsRespon w.Error(err) return } - w.JSON(aksk) + w.JSON(akskToDto(aksk)) } -func (akskCtl AkSkController) DeleteAksk(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, params api.DeleteAkskParams) { +func (akskCtl AkSkController) DeleteAksk(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, params api.DeleteAkskParams) { operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) @@ -91,15 +91,15 @@ func (akskCtl AkSkController) DeleteAksk(ctx context.Context, w *api.JiaozifsRes delParams.SetAccessKey(utils.StringValue(params.AccessKey)) } - aksk, err := akskCtl.Repo.AkskRepo().Delete(ctx, delParams) + _, err = akskCtl.Repo.AkskRepo().Delete(ctx, delParams) if err != nil { w.Error(err) return } - w.JSON(aksk) + w.OK() } -func (akskCtl AkSkController) ListAksks(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, params api.ListAksksParams) { +func (akskCtl AkSkController) ListAksks(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, params api.ListAksksParams) { operator, err := auth.GetOperator(ctx) if err != nil { w.Error(err) diff --git a/controller/user_ctl.go b/controller/user_ctl.go index 47abaf1a..23ba9a19 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -114,7 +114,7 @@ func (userCtl UserController) GetUserInfo(ctx context.Context, w *api.JiaozifsRe // Get token from Header user, err := auth.GetOperator(ctx) if err != nil { - w.Code(http.StatusForbidden) + w.Code(http.StatusUnauthorized) return } diff --git a/integrationtest/aksk_test.go b/integrationtest/aksk_test.go new file mode 100644 index 00000000..dc20dbf2 --- /dev/null +++ b/integrationtest/aksk_test.go @@ -0,0 +1,158 @@ +package integrationtest + +import ( + "context" + "net/http" + + "github.com/jiaozifs/jiaozifs/utils" + + "github.com/jiaozifs/jiaozifs/api" + apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" + "github.com/smartystreets/goconvey/convey" +) + +func AkSkSpec(ctx context.Context, urlStr string) func(c convey.C) { + client, _ := api.NewClient(urlStr + apiimpl.APIV1Prefix) + var aksk *api.Aksk + return func(c convey.C) { + userName := "muly" + createUser(ctx, c, client, userName) + loginAndSwitch(ctx, c, client, "muly login", userName, false) + + c.Convey("create aksk", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.CreateAksk(ctx, &api.CreateAkskParams{Description: utils.String("create ak sk")}) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("success create branch", func() { + resp, err := client.CreateAksk(ctx, &api.CreateAkskParams{Description: utils.String("create ak sk")}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + + akskResult, err := api.ParseCreateAkskResponse(resp) + convey.So(err, convey.ShouldBeNil) + aksk = akskResult.JSON201 + }) + }) + + c.Convey("get aksk", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.GetAksk(ctx, &api.GetAkskParams{Id: &aksk.Id}) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("get ak by id", func() { + resp, err := client.GetAksk(ctx, &api.GetAkskParams{Id: &aksk.Id}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + akskResult, err := api.ParseGetAkskResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.ShouldEqual(akskResult.JSON200.AccessKey, aksk.AccessKey) + convey.ShouldEqual(akskResult.JSON200.SecretKey, aksk.SecretKey) + convey.ShouldEqual(akskResult.JSON200.Description, aksk.Description) + }) + + c.Convey("get ak by ak", func() { + resp, err := client.GetAksk(ctx, &api.GetAkskParams{AccessKey: &aksk.AccessKey}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + akskResult, err := api.ParseGetAkskResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.ShouldEqual(akskResult.JSON200.Id, aksk.Id) + convey.ShouldEqual(akskResult.JSON200.SecretKey, aksk.SecretKey) + convey.ShouldEqual(akskResult.JSON200.Description, aksk.Description) + }) + c.Convey("get nothing by ak", func() { + resp, err := client.GetAksk(ctx, &api.GetAkskParams{AccessKey: utils.String("aaaa")}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + }) + + c.Convey("delete aksk", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.DeleteAksk(ctx, &api.DeleteAkskParams{Id: &aksk.Id}) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("delete ak by id", func() { + aksk, err := createAksk(ctx, client) + convey.So(err, convey.ShouldBeNil) + + resp, err := client.DeleteAksk(ctx, &api.DeleteAkskParams{Id: &aksk.Id}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + }) + + c.Convey("delete ak by ak", func() { + aksk, err := createAksk(ctx, client) + convey.So(err, convey.ShouldBeNil) + + resp, err := client.DeleteAksk(ctx, &api.DeleteAkskParams{AccessKey: &aksk.AccessKey}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + }) + c.Convey("delete nothing by ak", func() { + resp, err := client.DeleteAksk(ctx, &api.DeleteAkskParams{AccessKey: utils.String("fakekey")}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + }) + }) + + c.Convey("list aksk", func(c convey.C) { + c.Convey("prepare aksk", func() { + _, _ = createAksk(ctx, client) + _, _ = createAksk(ctx, client) + _, _ = createAksk(ctx, client) + _, _ = createAksk(ctx, client) + _, _ = createAksk(ctx, client) + }) + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.ListAksks(ctx, &api.ListAksksParams{}) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("delete nothing by ak", func() { + resp, err := client.ListAksks(ctx, &api.ListAksksParams{}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseListAksksResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.ShouldHaveLength(result.JSON200, 6) + }) + }) + } +} + +func createAksk(ctx context.Context, client *api.Client) (*api.Aksk, error) { + resp, err := client.CreateAksk(ctx, &api.CreateAkskParams{Description: utils.String("create ak sk")}) + if err != nil { + return nil, err + } + + akskResult, err := api.ParseCreateAkskResponse(resp) + if err != nil { + return nil, err + } + return akskResult.JSON201, nil +} diff --git a/integrationtest/root_test.go b/integrationtest/root_test.go index 918e082b..99fec43c 100644 --- a/integrationtest/root_test.go +++ b/integrationtest/root_test.go @@ -15,17 +15,14 @@ func TestSpec(t *testing.T) { convey.Convey("status test", t, StatusSpec(ctx, urlStr)) convey.Convey("user test", t, UserSpec(ctx, urlStr)) + convey.Convey("aksk test", t, AkSkSpec(ctx, urlStr)) convey.Convey("repo test", t, RepoSpec(ctx, urlStr)) convey.Convey("branch test", t, BranchSpec(ctx, urlStr)) - convey.Convey("object test", t, ObjectSpec(ctx, urlStr)) - convey.Convey("wip test", t, WipSpec(ctx, urlStr)) convey.Convey("wip object test", t, WipObjectSpec(ctx, urlStr)) convey.Convey("update wip test", t, UpdateWipSpec(ctx, urlStr)) - convey.Convey("get entries test", t, GetEntriesInRefSpec(ctx, urlStr)) convey.Convey("commit changes test", t, GetCommitChangesSpec(ctx, urlStr)) - convey.Convey("mergee request test", t, MergeRequestSpec(ctx, urlStr)) } diff --git a/integrationtest/user_test.go b/integrationtest/user_test.go index 980a4e6c..ea3bc54c 100644 --- a/integrationtest/user_test.go +++ b/integrationtest/user_test.go @@ -31,7 +31,7 @@ func UserSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("usr profile no cookie", func() { resp, err := client.GetUserInfo(ctx) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("login fail", func() { diff --git a/makefile b/makefile index e93ab23a..d324eb6d 100644 --- a/makefile +++ b/makefile @@ -24,7 +24,7 @@ swagger-srv: swagger serve $(SWAGGER_ARG) -F swagger ./api/swagger.yml lint: - golang-lint run ./... + golangci-lint run ./... test: gen-api go test -timeout=30m -parallel=4 -v ./... build:gen-api From c62ccbe8807aa0c619dd510430449ca4f553660d Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Fri, 23 Feb 2024 17:50:45 +0800 Subject: [PATCH 184/210] feat: add aksk example --- .gitignore | 2 +- api/jiaozifs.gen.go | 264 +++++++++++++------------- api/swagger.yml | 16 +- auth/aksk/sign.go | 2 +- auth/aksk/verifier.go | 3 +- auth/aksk/verifier_test.go | 27 +-- auth/auth_middleware.go | 102 +++++----- controller/merge_request_ctl.go | 4 +- controller/repository_ctl.go | 2 +- examples/aksk.go | 139 ++++++++++++++ integrationtest/helper_test.go | 2 +- integrationtest/merge_request_test.go | 2 +- integrationtest/repo_test.go | 12 +- utils/arr.go | 16 ++ utils/arr_test.go | 30 +++ versionmgr/work_repo.go | 52 ++--- 16 files changed, 428 insertions(+), 247 deletions(-) create mode 100644 examples/aksk.go create mode 100644 utils/arr.go create mode 100644 utils/arr_test.go diff --git a/.gitignore b/.gitignore index 9f63b47b..3c6d77fe 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,7 @@ jzfs # Test binary, built with `go test -c` *.test - +examples/examples # Test coverage file coverage.out diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index c7af4ed9..5be23d25 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -23,14 +23,14 @@ import ( ) const ( - Access_key_idScopes = "access_key_id.Scopes" - Basic_authScopes = "basic_auth.Scopes" - Cookie_authScopes = "cookie_auth.Scopes" - Jwt_tokenScopes = "jwt_token.Scopes" - SignatureScopes = "signature.Scopes" - Signature_methodScopes = "signature_method.Scopes" - Signature_versionScopes = "signature_version.Scopes" - TimestampScopes = "timestamp.Scopes" + JiaozifsAccessKeyIdScopes = "JiaozifsAccessKeyId.Scopes" + SignatureScopes = "Signature.Scopes" + SignatureMethodScopes = "SignatureMethod.Scopes" + SignatureVersionScopes = "SignatureVersion.Scopes" + TimestampScopes = "Timestamp.Scopes" + Basic_authScopes = "basic_auth.Scopes" + Cookie_authScopes = "cookie_auth.Scopes" + Jwt_tokenScopes = "jwt_token.Scopes" ) // Defines values for ChangeAction. @@ -4834,7 +4834,7 @@ func (r ListRepositoryOfAuthenticatedUserResponse) StatusCode() int { type CreateRepositoryResponse struct { Body []byte HTTPResponse *http.Response - JSON201 *[]Repository + JSON201 *Repository } // Status returns HTTPResponse.Status @@ -5008,7 +5008,7 @@ func (r GetWipChangesResponse) StatusCode() int { type CommitWipResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *Wip + JSON201 *Wip } // Status returns HTTPResponse.Status @@ -6206,7 +6206,7 @@ func ParseCreateRepositoryResponse(rsp *http.Response) (*CreateRepositoryRespons switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest []Repository + var dest Repository if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -6393,12 +6393,12 @@ func ParseCommitWipResponse(rsp *http.Response) (*CommitWipResponse, error) { } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: var dest Wip if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON200 = &dest + response.JSON201 = &dest } @@ -6867,13 +6867,13 @@ func (siw *ServerInterfaceWrapper) Logout(w http.ResponseWriter, r *http.Request ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -6927,13 +6927,13 @@ func (siw *ServerInterfaceWrapper) GetMergeRequest(w http.ResponseWriter, r *htt ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -6997,13 +6997,13 @@ func (siw *ServerInterfaceWrapper) UpdateMergeRequest(w http.ResponseWriter, r * ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -7048,13 +7048,13 @@ func (siw *ServerInterfaceWrapper) ListMergeRequests(w http.ResponseWriter, r *h ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -7136,13 +7136,13 @@ func (siw *ServerInterfaceWrapper) CreateMergeRequest(w http.ResponseWriter, r * ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -7206,13 +7206,13 @@ func (siw *ServerInterfaceWrapper) Merge(w http.ResponseWriter, r *http.Request) ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -7257,13 +7257,13 @@ func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.R ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -7341,13 +7341,13 @@ func (siw *ServerInterfaceWrapper) GetObject(w http.ResponseWriter, r *http.Requ ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -7461,13 +7461,13 @@ func (siw *ServerInterfaceWrapper) HeadObject(w http.ResponseWriter, r *http.Req ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -7581,13 +7581,13 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -7665,13 +7665,13 @@ func (siw *ServerInterfaceWrapper) DeleteRepository(w http.ResponseWriter, r *ht ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -7727,13 +7727,13 @@ func (siw *ServerInterfaceWrapper) GetRepository(w http.ResponseWriter, r *http. ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -7788,13 +7788,13 @@ func (siw *ServerInterfaceWrapper) UpdateRepository(w http.ResponseWriter, r *ht ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -7839,13 +7839,13 @@ func (siw *ServerInterfaceWrapper) DeleteBranch(w http.ResponseWriter, r *http.R ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -7908,13 +7908,13 @@ func (siw *ServerInterfaceWrapper) GetBranch(w http.ResponseWriter, r *http.Requ ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -7987,13 +7987,13 @@ func (siw *ServerInterfaceWrapper) CreateBranch(w http.ResponseWriter, r *http.R ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -8038,13 +8038,13 @@ func (siw *ServerInterfaceWrapper) ListBranches(w http.ResponseWriter, r *http.R ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -8125,13 +8125,13 @@ func (siw *ServerInterfaceWrapper) GetCommitChanges(w http.ResponseWriter, r *ht ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -8187,13 +8187,13 @@ func (siw *ServerInterfaceWrapper) GetCommitsInRef(w http.ResponseWriter, r *htt ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -8274,13 +8274,13 @@ func (siw *ServerInterfaceWrapper) CompareCommit(w http.ResponseWriter, r *http. ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -8336,13 +8336,13 @@ func (siw *ServerInterfaceWrapper) GetEntriesInRef(w http.ResponseWriter, r *htt ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -8418,13 +8418,13 @@ func (siw *ServerInterfaceWrapper) DeleteAksk(w http.ResponseWriter, r *http.Req ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -8470,13 +8470,13 @@ func (siw *ServerInterfaceWrapper) GetAksk(w http.ResponseWriter, r *http.Reques ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -8522,13 +8522,13 @@ func (siw *ServerInterfaceWrapper) CreateAksk(w http.ResponseWriter, r *http.Req ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -8566,13 +8566,13 @@ func (siw *ServerInterfaceWrapper) ListAksks(w http.ResponseWriter, r *http.Requ ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -8662,13 +8662,13 @@ func (siw *ServerInterfaceWrapper) ListRepositoryOfAuthenticatedUser(w http.Resp ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -8730,13 +8730,13 @@ func (siw *ServerInterfaceWrapper) CreateRepository(w http.ResponseWriter, r *ht ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -8761,13 +8761,13 @@ func (siw *ServerInterfaceWrapper) GetUserInfo(w http.ResponseWriter, r *http.Re ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -8803,13 +8803,13 @@ func (siw *ServerInterfaceWrapper) ListRepository(w http.ResponseWriter, r *http ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -8896,13 +8896,13 @@ func (siw *ServerInterfaceWrapper) DeleteWip(w http.ResponseWriter, r *http.Requ ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -8965,13 +8965,13 @@ func (siw *ServerInterfaceWrapper) GetWip(w http.ResponseWriter, r *http.Request ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -9044,13 +9044,13 @@ func (siw *ServerInterfaceWrapper) UpdateWip(w http.ResponseWriter, r *http.Requ ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -9113,13 +9113,13 @@ func (siw *ServerInterfaceWrapper) GetWipChanges(w http.ResponseWriter, r *http. ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -9190,13 +9190,13 @@ func (siw *ServerInterfaceWrapper) CommitWip(w http.ResponseWriter, r *http.Requ ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -9274,13 +9274,13 @@ func (siw *ServerInterfaceWrapper) ListWip(w http.ResponseWriter, r *http.Reques ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -9325,13 +9325,13 @@ func (siw *ServerInterfaceWrapper) RevertWipChanges(w http.ResponseWriter, r *ht ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - ctx = context.WithValue(ctx, Access_key_idScopes, []string{}) + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) ctx = context.WithValue(ctx, SignatureScopes, []string{}) - ctx = context.WithValue(ctx, Signature_methodScopes, []string{}) + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - ctx = context.WithValue(ctx, Signature_versionScopes, []string{}) + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) ctx = context.WithValue(ctx, TimestampScopes, []string{}) @@ -9695,17 +9695,17 @@ var swaggerSpec = []string{ "7fM6laUv5XBrhanSuppQ938KdVAeoEOudezvD/W/0S3uL0yzbYvuCtgrzjyJcD22AuxWAg4RBzErrkRy", "6sKxaWSudXlQt8hY8s1HI3/eJuO6Tea6eufVj3NliLUbtX6c39T8yBpL9TY2YRyQvs/aeatKrkjmMsDu", "+2fy6wK3VWLUvJGwuzi0GeRVLxlClyaQ3+IQ2YpvtFMRK3oYtwQpWaDqhPpFlrL+BaAs2Po1qhgnhIrZ", - "d5wAfsiLSePSZAdmaTh+KKVqTWJcsf0e52/r1YKtYbZQdXL7S6hbMqYwfzAitk7gsmpEAwTq374oS3H1", - "7xZNqBjDwdiTcsHXh/Iig3Om+UDu3XqfRKjZ1Va/nGWu54z1t82mEO4Q/aUC3ge6eVB9FfD9ibQrIG0l", - "mF668A8EafXHTvIcRh5gvZO8qg6nVq4J7TL18obQrYmwfuGy63R349rjPsfHJoLyVzANkeNSZlcAdE7S", - "NY+dfNef41jneMicpPerjxwSdgn6+52ETpU6ppxph7fkkiKyL1jYPf2NqIfq3qEUDpLv/VDIIDYut+aN", - "Jn3XStu0ql6GVbps7PxJrlLbOnhS6NT61xS2Zb1esfGWjp0Unwiq6F4fyo0q3wnsMfTOysata8zQnHz+", - "+fonUCPjULFcSg8Q6lD5Gb/VIe8hJJe7TaP4BMLjOyR/l9htiuEMdvfCg62MyT9M7SYtEdMHcxyqwwex", - "88j9Ou1sWhKQ/hY6hfm9+Hi+98p1DdCgSyPMnBz2LVn7LMmAhSW2nw3r3NduwH8cBLzfjSBWR90HsGWc", - "k7S2V0w503cNKJVrRBieCOhyuAQ+EHT/Axxmv/0tIIjIFWIRWuLx2IjPWoh+rIVQ8/tW2uYaId4bBDqG", - "s0G9Isjniu5ZqitHPtqY4CNQjqhmvrnF0b6F47iNj0szcNUv4Lhzcn7rkzs68FF+e6b2Z/ExmfrTIuCj", - "Hle+8GJzfpo3rgW7UkJmFg4apsxc2V1+nuVwNIpZgOMZE/Lwxcu/7r8Y4ZSMLve9tvYu7bB49fzm/wMA", - "AP//KxKoD8WUAAA=", + "d5wAfsiLSePSZAdmaTh+KKVqTWJcsf0e52/r1YKtYe646qS/NJXC/MFI0vp6y4oOjb2rf/uCKcUNv1u0", + "lGIMB2NPynVdn72LDJyZ5gO5d+vtEKFm81r9QJa5hTPWnzCbQrhD9AcJeB+25rHzVTD2J6CuAKiVmHnp", + "qT8QQNXfNMlTFXkc9U7SpzpqWrkNtMvUy4tAtybC+r3KrkPcjduN+/wbm+/JX8E0RI67l11xzjlJ1zxd", + "8l1/dWOdUyBzkt6vPnJI2CXoz3QSOlXqmHKm/dqSS4rIvphg9/Q3oh6qe4dSOEi+97Mfg9i43Jo3mttd", + "KzvTKm4ZVtCysWMmuUpt63xJoVPr30bYlvV6NcVbOl1SfAmoont9KDeqfA6wx9A7Cxi3rjFDU+/5V+qf", + "QCmMQ8VyKT1AqEPl1/pWh7yHkEPuNo3iSweP7yz8XWK3qXkz2N0LD7YAJv/+tJu0RExveeppf9s+iJ1H", + "7tdpZ9OSgPQnzynM78XH871Xrtt+Bt0NYebksG/J2kdGBiwssf06WOe+dgP+4yDg/W4EsTrqPoAt45yk", + "tb1iypm+UkCpXCPC8ERAl8Ml8IGg+x/gMPvtT/5ARK4Qi9ASj8dGfNZC9GMthJrft9I21wjx3iDQMZwN", + "6hVBPld0z1JdOdnRxgQfgXJENfPNZY32LRzHbXxcmmirfujGnXrzr50fzdHhj8pn+mp/2i/C1B/mER39", + "tPxWS57e0/xxLdqVajGzeNAwZeZ27vJLLIejUcwCHM+YkIcvXv51/8UIp2R0ue+1NXhph8Wr5zf/HwAA", + "///6wwPHsJQAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 780c28de..225a8506 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -17,11 +17,11 @@ security: - jwt_token: [] # Default security for the entire API - basic_auth: [] - cookie_auth: [] - - access_key_id: [] - signature_version: [] - signature_method: [] - timestamp: [] - signature: [] + - JiaozifsAccessKeyId: [] + SignatureVersion: [] + SignatureMethod: [] + Timestamp: [] + Signature: [] components: parameters: PaginationPrefix: @@ -1400,7 +1400,7 @@ paths: schema: type: string responses: - 200: + 201: description: commit success and response with new wip content: application/json: @@ -1947,9 +1947,7 @@ paths: content: application/json: schema: - type: array - items: - $ref: "#/components/schemas/Repository" + $ref: "#/components/schemas/Repository" 400: description: ValidationError 401: diff --git a/auth/aksk/sign.go b/auth/aksk/sign.go index ca459b08..1c75eba4 100644 --- a/auth/aksk/sign.go +++ b/auth/aksk/sign.go @@ -45,7 +45,7 @@ func (voSigner V0Signer) Sign(req *http.Request) error { query := req.URL.Query() query.Set(AccessKeykey, voSigner.accessKey) query.Set(SignatureVersionKey, signatureVersion) - query.Set(SignatureVersionKey, signatureMethod) + query.Set(SignatureMethodKey, signatureMethod) query.Set(TimestampKey, curTime.UTC().Format(timeFormat)) req.Header.Del("Signature") diff --git a/auth/aksk/verifier.go b/auth/aksk/verifier.go index 39474a2d..728bac20 100644 --- a/auth/aksk/verifier.go +++ b/auth/aksk/verifier.go @@ -72,7 +72,7 @@ func (v *V0Verier) Verify(req *http.Request) (string, error) { query.Del(SignatureKey) method := req.Method - host := req.URL.Host + host := req.Host path := req.URL.Path if path == "" { path = "/" @@ -103,6 +103,7 @@ func (v *V0Verier) Verify(req *http.Request) (string, error) { path, queryString, }, "\n") + hash := hmac.New(sha256.New, []byte(secretKey)) hash.Write([]byte(stringToSign)) actualSig := base64.StdEncoding.EncodeToString(hash.Sum(nil)) diff --git a/auth/aksk/verifier_test.go b/auth/aksk/verifier_test.go index 13b0005d..e26662dc 100644 --- a/auth/aksk/verifier_test.go +++ b/auth/aksk/verifier_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestFull(t *testing.T) { +func TestAkSK(t *testing.T) { ak, sk, err := GenerateAksk() require.NoError(t, err) @@ -52,25 +52,12 @@ func TestFull(t *testing.T) { require.NoError(t, err) query := req.URL.Query() - query.Del("AWSAccessKeyId") + query.Del(AccessKeykey) req.URL.RawQuery = query.Encode() _, err = verifer.Verify(req) require.Error(t, err) }) - t.Run("sig method fail", func(t *testing.T) { - req := mockHttpRequest() - signer := NewV0Signer(ak, sk) - verifer := NewV0Verier(skGetter{sk}) - - err = signer.Sign(req) - require.NoError(t, err) - query := req.URL.Query() - query.Set("SignatureMethod", "2") - req.URL.RawQuery = query.Encode() - _, err = verifer.Verify(req) - require.Error(t, err) - }) t.Run("sig method fail", func(t *testing.T) { req := mockHttpRequest() signer := NewV0Signer(ak, sk) @@ -80,7 +67,7 @@ func TestFull(t *testing.T) { require.NoError(t, err) query := req.URL.Query() - query.Set("SignatureMethod", "md5") + query.Set(SignatureMethodKey, "md5") req.URL.RawQuery = query.Encode() _, err = verifer.Verify(req) require.Error(t, err) @@ -94,7 +81,7 @@ func TestFull(t *testing.T) { require.NoError(t, err) query := req.URL.Query() - query.Set("SignatureVersion", "2") + query.Set(SignatureVersionKey, "2") req.URL.RawQuery = query.Encode() _, err = verifer.Verify(req) require.Error(t, err) @@ -108,7 +95,7 @@ func TestFull(t *testing.T) { require.NoError(t, err) query := req.URL.Query() - query.Del("Timestamp") + query.Del(TimestampKey) req.URL.RawQuery = query.Encode() _, err = verifer.Verify(req) require.Error(t, err) @@ -123,7 +110,7 @@ func TestFull(t *testing.T) { require.NoError(t, err) query := req.URL.Query() - query.Set("Timestamp", time.Now().String()) + query.Set(TimestampKey, time.Now().String()) req.URL.RawQuery = query.Encode() _, err = verifer.Verify(req) require.Error(t, err) @@ -137,7 +124,7 @@ func TestFull(t *testing.T) { require.NoError(t, err) query := req.URL.Query() - query.Set("Timestamp", time.Now().Add(-time.Minute*10).UTC().Format(timeFormat)) + query.Set(TimestampKey, time.Now().Add(-time.Minute*10).UTC().Format(timeFormat)) req.URL.RawQuery = query.Encode() _, err = verifer.Verify(req) require.Error(t, err) diff --git a/auth/auth_middleware.go b/auth/auth_middleware.go index 85f04e05..6d9dd724 100644 --- a/auth/auth_middleware.go +++ b/auth/auth_middleware.go @@ -7,6 +7,8 @@ import ( "net/http" "strings" + "github.com/jiaozifs/jiaozifs/utils" + "github.com/jiaozifs/jiaozifs/auth/aksk" "github.com/golang-jwt/jwt/v5" @@ -112,57 +114,55 @@ func checkSecurityRequirements(r *http.Request, var err error for _, securityRequirement := range securityRequirements { - for provider := range securityRequirement { - switch provider { - case "jwt_token": - // validate jwt token from header - authHeaderValue := r.Header.Get("Authorization") - if authHeaderValue == "" { - continue - } - parts := strings.Fields(authHeaderValue) - if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") { - continue - } - token := parts[1] - user, err = userByToken(ctx, userRepo, secretStore.SharedSecret(), token) - case "basic_auth": - // validate using basic auth - userName, password, ok := r.BasicAuth() - if !ok { - continue - } - - user, err = userByAuth(ctx, authenticator, userName, password) - case "cookie_auth": - var internalAuthSession *sessions.Session - internalAuthSession, _ = sessionStore.Get(r, InternalAuthSessionName) - token := "" - if internalAuthSession != nil { - token, _ = internalAuthSession.Values[TokenSessionKeyName].(string) - } - if token == "" { - continue - } - user, err = userByToken(ctx, userRepo, secretStore.SharedSecret(), token) - case "ak_sk": - isAkskRequest := verifier.IsAkskCredential(r) - if !isAkskRequest { - continue - } - user, err = userByAKSK(ctx, akskRepo, userRepo, verifier, r) - default: - // unknown security requirement to check - log.With("provider", provider).Error("Authentication middleware unknown security requirement provider") - return nil, ErrAuthenticatingRequest + securityKeys := getSecurityKey(securityRequirement) + if utils.Contain(securityKeys, "jwt_token") { + // validate jwt token from header + authHeaderValue := r.Header.Get("Authorization") + if authHeaderValue == "" { + continue + } + parts := strings.Fields(authHeaderValue) + if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") { + continue + } + token := parts[1] + user, err = userByToken(ctx, userRepo, secretStore.SharedSecret(), token) + } else if utils.Contain(securityKeys, "basic_auth") { + // validate using basic auth + userName, password, ok := r.BasicAuth() + if !ok { + continue } - if err != nil { - return nil, err + user, err = userByAuth(ctx, authenticator, userName, password) + } else if utils.Contain(securityKeys, "cookie_auth") { + var internalAuthSession *sessions.Session + internalAuthSession, _ = sessionStore.Get(r, InternalAuthSessionName) + token := "" + if internalAuthSession != nil { + token, _ = internalAuthSession.Values[TokenSessionKeyName].(string) } - if user != nil { - return user, nil + if token == "" { + continue + } + user, err = userByToken(ctx, userRepo, secretStore.SharedSecret(), token) + } else if utils.Contain(securityKeys, aksk.AccessKeykey) { + isAkskRequest := verifier.IsAkskCredential(r) + if !isAkskRequest { + continue } + user, err = userByAKSK(ctx, akskRepo, userRepo, verifier, r) + } else { + // unknown security requirement to check + log.With("provider", securityKeys).Error("Authentication middleware unknown security requirement provider") + return nil, ErrAuthenticatingRequest + } + + if err != nil { + return nil, err + } + if user != nil { + return user, nil } } return nil, nil @@ -222,3 +222,11 @@ func userByAuth(ctx context.Context, authenticator *BasicAuthenticator, accessKe } return user, nil } + +func getSecurityKey(security openapi3.SecurityRequirement) []string { + var keys []string + for key := range security { + keys = append(keys, key) + } + return keys +} diff --git a/controller/merge_request_ctl.go b/controller/merge_request_ctl.go index 736f0111..76a6f9ee 100644 --- a/controller/merge_request_ctl.go +++ b/controller/merge_request_ctl.go @@ -386,12 +386,12 @@ func (mrCtl MergeRequestController) Merge(ctx context.Context, w *api.JiaozifsRe return err } - err = workRepo.CheckOut(ctx, versionmgr.InBranch, sourceBranch.Name) + err = workRepo.CheckOut(ctx, versionmgr.InBranch, targetBranch.Name) if err != nil { return err } - commit, err = workRepo.Merge(ctx, targetBranch.CommitHash, body.Msg, versionmgr.ResolveFromSelector(utils.Map(body.ConflictResolve))) + commit, err = workRepo.Merge(ctx, sourceBranch.CommitHash, body.Msg, versionmgr.ResolveFromSelector(utils.Map(body.ConflictResolve))) if err != nil { return err } diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index b7019407..0f51582d 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -205,7 +205,7 @@ func (repositoryCtl RepositoryController) CreateRepository(ctx context.Context, return } - w.JSON(repositoryToDto(createdRepo)) + w.JSON(repositoryToDto(createdRepo), http.StatusCreated) } func (repositoryCtl RepositoryController) DeleteRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.DeleteRepositoryParams) { diff --git a/examples/aksk.go b/examples/aksk.go new file mode 100644 index 00000000..fa7b9d9c --- /dev/null +++ b/examples/aksk.go @@ -0,0 +1,139 @@ +package main + +import ( + "bytes" + "context" + "flag" + "log" + + "github.com/jiaozifs/jiaozifs/utils" + + "github.com/jiaozifs/jiaozifs/api" + apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" +) + +var url string +var ak string +var sk string +var repoName string + +func init() { + flag.StringVar(&url, "url", "", "jiaozifs endpoint") + flag.StringVar(&ak, "ak", "", "jiaozifs ak") + flag.StringVar(&sk, "sk", "", "jiaozifs sk") + flag.StringVar(&repoName, "repo", "", "repo to create") +} +func main() { + flag.Parse() + log.Println(func(ctx context.Context) error { + cli, err := api.NewClient(url+apiimpl.APIV1Prefix, api.AkSkOption(ak, sk)) + if err != nil { + return err + } + resp, err := cli.GetUserInfo(ctx) + userInfo, err := api.ParseGetUserInfoResponse(resp) + if err != nil { + return err + } + log.Println("User", userInfo.JSON200.Name, "Login") + + //create repo + resp, err = cli.CreateRepository(ctx, api.CreateRepositoryJSONRequestBody{ + Name: repoName, + }) + if err != nil { + return err + } + repo, err := api.ParseCreateRepositoryResponse(resp) + if err != nil { + return err + } + log.Println("Create Repo", repo.JSON201.Name, "ID:", repo.JSON201.Id) + + //create branch + branchName := "branch_test" + resp, err = cli.CreateBranch(ctx, userInfo.JSON200.Name, repo.JSON201.Name, api.CreateBranchJSONRequestBody{ + Name: branchName, + Source: "main", + }) + if err != nil { + return err + } + _, err = api.ParseCreateBranchResponse(resp) + if err != nil { + return err + } + log.Println("Create Branch", repo.JSON201.Name, "ID:", repo.JSON201.Id) + + //create draft + resp, err = cli.GetWip(ctx, userInfo.JSON200.Name, repo.JSON201.Name, &api.GetWipParams{RefName: branchName}) + if err != nil { + return err + } + log.Println("create draft for main branch") + // upload files to draft + resp, err = cli.UploadObjectWithBody(ctx, userInfo.JSON200.Name, repo.JSON201.Name, &api.UploadObjectParams{ + RefName: branchName, + Path: "a.bin", + }, "application/octet-stream", bytes.NewBufferString("hello, world!!")) + if err != nil { + return err + } + + uploadResult, err := api.ParseUploadObjectResponse(resp) + if err != nil { + return err + } + log.Println("upload a file size:", uploadResult.JSON201.SizeBytes, "checksum", uploadResult.JSON201.Checksum) + + //commit draft + resp, err = cli.CommitWip(ctx, userInfo.JSON200.Name, repo.JSON201.Name, &api.CommitWipParams{ + RefName: branchName, + Msg: "test", + }) + if err != nil { + return err + } + commitResult, err := api.ParseCommitWipResponse(resp) + if err != nil { + return err + } + log.Println("commit changes. hash:", commitResult.JSON201.BaseCommit) + // merge branch + + resp, err = cli.CreateMergeRequest(ctx, userInfo.JSON200.Name, repo.JSON201.Name, api.CreateMergeRequestJSONRequestBody{ + Description: utils.String("create merge request test"), + SourceBranchName: branchName, + TargetBranchName: "main", + Title: "Merge: test", + }) + if err != nil { + return err + } + mergeRequestResult, err := api.ParseCreateMergeRequestResponse(resp) + if err != nil { + return err + } + log.Println("create merge request merge id", mergeRequestResult.JSON201.Sequence) + + _, err = cli.Merge(ctx, userInfo.JSON200.Name, repo.JSON201.Name, mergeRequestResult.JSON201.Sequence, api.MergeJSONRequestBody{ + Msg: "merge it", + }) + log.Println("merge success") + + //read the file from main branch + resp, err = cli.GetObject(ctx, userInfo.JSON200.Name, repo.JSON201.Name, &api.GetObjectParams{ + Type: api.RefTypeBranch, + RefName: "main", + Path: "a.bin", + Range: nil, + }) + if err != nil { + return err + } + + result, err := api.ParseGetObjectResponse(resp) + log.Println("get object content ", string(result.Body)) + return nil + }(context.Background())) +} diff --git a/integrationtest/helper_test.go b/integrationtest/helper_test.go index 2817c5fb..33d75802 100644 --- a/integrationtest/helper_test.go +++ b/integrationtest/helper_test.go @@ -159,7 +159,7 @@ func createRepo(ctx context.Context, c convey.C, client *api.Client, repoName st Name: repoName, }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) }) } diff --git a/integrationtest/merge_request_test.go b/integrationtest/merge_request_test.go index 1946fe44..00d2403e 100644 --- a/integrationtest/merge_request_test.go +++ b/integrationtest/merge_request_test.go @@ -350,7 +350,7 @@ func MergeRequestSpec(ctx context.Context, urlStr string) func(c convey.C) { }) }) - c.Convey(" merge request", func(c convey.C) { + c.Convey("merge request", func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index 39861142..58b0a571 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -58,14 +58,14 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { Name: repoName, }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) - grp, err := api.ParseGetRepositoryResponse(resp) + grp, err := api.ParseCreateRepositoryResponse(resp) convey.So(err, convey.ShouldBeNil) - convey.So(grp.JSON200.Head, convey.ShouldEqual, controller.DefaultBranchName) - fmt.Println(grp.JSON200.Id) + convey.So(grp.JSON201.Head, convey.ShouldEqual, controller.DefaultBranchName) + fmt.Println(grp.JSON201.Id) //check default branch created - branchResp, err := client.GetBranch(ctx, userName, grp.JSON200.Name, &api.GetBranchParams{RefName: controller.DefaultBranchName}) + branchResp, err := client.GetBranch(ctx, userName, grp.JSON201.Name, &api.GetBranchParams{RefName: controller.DefaultBranchName}) convey.So(err, convey.ShouldBeNil) convey.So(branchResp.StatusCode, convey.ShouldEqual, http.StatusOK) @@ -80,7 +80,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { Name: "happygo", }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) }) c.Convey("duplicate repo name", func() { diff --git a/utils/arr.go b/utils/arr.go new file mode 100644 index 00000000..1878af85 --- /dev/null +++ b/utils/arr.go @@ -0,0 +1,16 @@ +package utils + +type numbers interface { + int | int8 | int16 | int32 | int64 | float32 | float64 +} + +func Contain[T interface { + numbers | ~string | ~bool +}](items []T, ele T) bool { + for _, v := range items { + if v == ele { + return true + } + } + return false +} diff --git a/utils/arr_test.go b/utils/arr_test.go new file mode 100644 index 00000000..d5506062 --- /dev/null +++ b/utils/arr_test.go @@ -0,0 +1,30 @@ +package utils + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestContain(t *testing.T) { + + t.Run("string", func(t *testing.T) { + require.True(t, Contain([]string{"a", "b", "c"}, "a")) + require.False(t, Contain([]string{"a", "b", "c"}, "d")) + }) + + t.Run("int", func(t *testing.T) { + require.True(t, Contain([]int{1, 2, 3}, 1)) + require.False(t, Contain([]int{1, 2, 3}, 4)) + }) + + t.Run("float", func(t *testing.T) { + require.True(t, Contain([]float64{1.0, 2.0, 3.0}, 1.0)) + require.False(t, Contain([]float64{1.0, 2.0, 3.0}, 4.0)) + }) + + t.Run("bool", func(t *testing.T) { + require.True(t, Contain([]bool{true, false}, true)) + require.False(t, Contain([]bool{true, true}, false)) + }) +} diff --git a/versionmgr/work_repo.go b/versionmgr/work_repo.go index a17e450b..8f30a444 100644 --- a/versionmgr/work_repo.go +++ b/versionmgr/work_repo.go @@ -701,18 +701,20 @@ func (repository *WorkRepository) Merge(ctx context.Context, toMergeCommitHash h if repository.state != InBranch { return nil, errors.New("must merge on branch") } - var commit *models.Commit + var targetCommit *models.Commit var err error if !repository.branch.CommitHash.IsEmpty() { - commit, err = repository.repo.CommitRepo(repository.repoModel.ID).Commit(ctx, repository.branch.CommitHash) + //get branch commit + targetCommit, err = repository.repo.CommitRepo(repository.repoModel.ID).Commit(ctx, repository.branch.CommitHash) if err != nil { return nil, err } } - var toMergeCommit *models.Commit + var sourceCommit *models.Commit if !toMergeCommitHash.IsEmpty() { - toMergeCommit, err = repository.repo.CommitRepo(repository.repoModel.ID).Commit(ctx, toMergeCommitHash) + //get toMergeCommit + sourceCommit, err = repository.repo.CommitRepo(repository.repoModel.ID).Commit(ctx, toMergeCommitHash) if err != nil { return nil, err } @@ -722,11 +724,11 @@ func (repository *WorkRepository) Merge(ctx context.Context, toMergeCommitHash h err = repository.repo.Transaction(ctx, func(repo models.IRepo) error { commitRepo := repo.CommitRepo(repository.repoModel.ID) fileTreeRepo := repo.FileTreeRepo(repository.repoModel.ID) - bestAncestor, err := findBestAncestor(ctx, commitRepo, fileTreeRepo, repository.operator, repository.repoModel, commit, toMergeCommit) + bestAncestor, err := findBestAncestor(ctx, commitRepo, fileTreeRepo, repository.operator, repository.repoModel, sourceCommit, targetCommit) if err != nil { return err } - newCommit, err = merge(ctx, commitRepo, fileTreeRepo, repository.repoModel, repository.operator, bestAncestor, commit, toMergeCommit, msg, resolver) + newCommit, err = merge(ctx, commitRepo, fileTreeRepo, repository.repoModel, repository.operator, bestAncestor, sourceCommit, targetCommit, msg, resolver) if err != nil { return err } @@ -819,49 +821,49 @@ func merge(ctx context.Context, repoModel *models.Repository, merger *models.User, bestAncestor *models.Commit, - baseCommit *models.Commit, - toMergeCommit *models.Commit, + sourceCommit *models.Commit, + targetCommit *models.Commit, msg string, resolver ConflictResolver) (*models.Commit, error) { - if baseCommit == nil && toMergeCommit == nil { + if sourceCommit == nil && targetCommit == nil { return nil, errors.New("cannot find nil commit") } - if baseCommit != nil && toMergeCommit == nil { + if sourceCommit != nil && targetCommit == nil { //do nothing - return baseCommit, nil + return sourceCommit, nil } - if baseCommit == nil && toMergeCommit != nil { - return toMergeCommit, nil + if sourceCommit == nil && targetCommit != nil { + return targetCommit, nil } - baseCommitNode := NewWrapCommitNode(commitRepo, baseCommit) - toMergeCommitNode := NewWrapCommitNode(commitRepo, toMergeCommit) + baseCommitNode := NewWrapCommitNode(commitRepo, sourceCommit) + targetMergeCommitNode := NewWrapCommitNode(commitRepo, targetCommit) { //do nothing while merge is ancestor of base - mergeIsAncestorOfBase, err := toMergeCommitNode.IsAncestor(ctx, baseCommitNode) + mergeIsAncestorOfBase, err := targetMergeCommitNode.IsAncestor(ctx, baseCommitNode) if err != nil { return nil, err } if mergeIsAncestorOfBase { - workRepoLog.Warnf("merge commit %s is ancestor of base commit %s", toMergeCommit.Hash, baseCommit.Hash) - return baseCommit, nil + workRepoLog.Warnf("merge commit %s is ancestor of base commit %s", targetCommit.Hash, sourceCommit.Hash) + return sourceCommit, nil } } { //try fast-forward merge no need to create new commit node - baseIsAncestorOfMerge, err := baseCommitNode.IsAncestor(ctx, toMergeCommitNode) + baseIsAncestorOfMerge, err := baseCommitNode.IsAncestor(ctx, targetMergeCommitNode) if err != nil { return nil, err } if baseIsAncestorOfMerge { - workRepoLog.Warnf("base commit %s is ancestor of merge commit %s", toMergeCommit.Hash, baseCommit.Hash) - return toMergeCommit, nil + workRepoLog.Warnf("base commit %s is ancestor of merge commit %s", targetCommit.Hash, sourceCommit.Hash) + return targetCommit, nil } } @@ -870,12 +872,12 @@ func merge(ctx context.Context, return nil, err } - baseDiff, err := ancestorWorkTree.Diff(ctx, baseCommit.TreeHash, "") + sourceDiff, err := ancestorWorkTree.Diff(ctx, sourceCommit.TreeHash, "") if err != nil { return nil, err } - mergeDiff, err := ancestorWorkTree.Diff(ctx, toMergeCommit.TreeHash, "") + targetDiff, err := ancestorWorkTree.Diff(ctx, targetCommit.TreeHash, "") if err != nil { return nil, err } @@ -886,7 +888,7 @@ func merge(ctx context.Context, return nil, err } - cmw := NewChangesMergeIter(baseDiff, mergeDiff, resolver) + cmw := NewChangesMergeIter(sourceDiff, targetDiff, resolver) for cmw.Has() { change, err := cmw.Next() if err != nil { @@ -912,7 +914,7 @@ func merge(ctx context.Context, MergeTag: "", Message: msg, TreeHash: baseWorkTree.Root().Hash(), - ParentHashes: []hash.Hash{baseCommit.Hash, toMergeCommit.Hash}, + ParentHashes: []hash.Hash{sourceCommit.Hash, targetCommit.Hash}, CreatedAt: time.Now(), UpdatedAt: time.Now(), } From e5745429846f7b8520874105256cc7d6a0b5c00e Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Fri, 23 Feb 2024 18:04:44 +0800 Subject: [PATCH 185/210] test: add aksk usage --- codecov.yml | 1 + integrationtest/aksk_test.go | 37 ++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/codecov.yml b/codecov.yml index 23972b97..18ac0b51 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,2 +1,3 @@ ignore: - "**/*.gen.go" + - "examples" diff --git a/integrationtest/aksk_test.go b/integrationtest/aksk_test.go index dc20dbf2..29443cea 100644 --- a/integrationtest/aksk_test.go +++ b/integrationtest/aksk_test.go @@ -141,6 +141,43 @@ func AkSkSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.ShouldHaveLength(result.JSON200, 6) }) }) + + c.Convey("aksk usage", func(c convey.C) { + c.Convey("success", func(c convey.C) { + aksk, err := createAksk(ctx, client) + convey.So(err, convey.ShouldBeNil) + + cli, err := api.NewClient(urlStr+apiimpl.APIV1Prefix, api.AkSkOption(aksk.AccessKey, aksk.SecretKey)) + convey.So(err, convey.ShouldBeNil) + + resp, err := cli.GetUserInfo(ctx) + convey.So(err, convey.ShouldBeNil) + + user, err := api.ParseGetUserInfoResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.ShouldEqual(user.JSON200.Name, userName) + }) + c.Convey("wrong sk", func(c convey.C) { + aksk, err := createAksk(ctx, client) + convey.So(err, convey.ShouldBeNil) + + client, err := api.NewClient(urlStr+apiimpl.APIV1Prefix, api.AkSkOption(aksk.AccessKey, "fakesk")) + convey.So(err, convey.ShouldBeNil) + + resp, err := client.GetUserInfo(ctx) + convey.So(err, convey.ShouldBeNil) + convey.ShouldEqual(resp.StatusCode, http.StatusUnauthorized) + }) + + c.Convey("ak not exit", func(c convey.C) { + client, err := api.NewClient(urlStr+apiimpl.APIV1Prefix, api.AkSkOption("fakesk", "fakesk")) + convey.So(err, convey.ShouldBeNil) + + resp, err := client.GetUserInfo(ctx) + convey.So(err, convey.ShouldBeNil) + convey.ShouldEqual(resp.StatusCode, http.StatusUnauthorized) + }) + }) } } From b58069186e1b93dfff708568125e7b1438aeaaec Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Mon, 4 Mar 2024 12:52:46 +0800 Subject: [PATCH 186/210] Feat/support rbac (#129) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: init * add rbac_role.drawio * feat: add basic design * 更新rbac_role.drawio * feat: add permission check * feat: support api and integration test * chore: make lint happy * docs: add rbac and auth docs * chore: make lint happy * 更新rbac_role.drawio --- api/api_impl/impl.go | 3 + api/jiaozifs.gen.go | 7661 ++++++++++------- api/swagger.yml | 226 +- auth/auth_middleware.go | 3 + auth/basic_auth.go | 61 - auth/rbac/arn.go | 119 + auth/rbac/arn_test.go | 99 + auth/rbac/rbac.go | 359 + auth/rbac/rbac_test.go | 295 + auth/rbac/statements.go | 150 + auth/rbac/wildcard/match.go | 89 + auth/rbac/wildcard/match_test.go | 302 + auth/types.go | 12 +- block/local/adapter.go | 1 - cmd/daemon.go | 7 + cmd/init.go | 88 +- controller/aksk_ctl.go | 56 +- controller/base_ctl.go | 75 + controller/branch_ctl.go | 69 +- controller/commit_ctl.go | 27 +- controller/group.go | 50 + controller/member.go | 163 + controller/merge_request_ctl.go | 80 +- controller/object_ctl.go | 89 +- controller/repository_ctl.go | 114 +- controller/user_ctl.go | 107 +- controller/wip_ctl.go | 96 +- docs/authorization.md | 37 + docs/{ => draws}/jiaozifs_aim.drawio | 0 docs/{ => draws}/jiaozifs_mind.drawio | 0 docs/draws/rbac_role.drawio | 308 + docs/rbac.md | 166 + go.mod | 1 + go.sum | 2 + integrationtest/aksk_test.go | 20 +- integrationtest/branch_test.go | 25 +- integrationtest/commit_test.go | 78 +- integrationtest/group_test.go | 25 + integrationtest/helper_test.go | 190 +- integrationtest/member_test.go | 385 + integrationtest/merge_request_test.go | 44 +- integrationtest/objects_test.go | 26 +- integrationtest/repo_test.go | 57 +- integrationtest/root_test.go | 4 +- integrationtest/user_test.go | 21 +- integrationtest/wip_object_test.go | 67 +- integrationtest/wip_test.go | 28 +- makefile | 2 +- models/aksk_test.go | 2 +- models/branch_test.go | 4 +- models/commit_test.go | 2 +- models/members.go | 212 + models/members_test.go | 121 + models/merge_request_test.go | 2 +- .../migrations/20210505110026_init_project.go | 30 + models/models.go | 24 +- models/rbacmodel/actions.gen.go | 58 + models/rbacmodel/actions.go | 96 + models/rbacmodel/actions_test.go | 24 + models/rbacmodel/codegen/main.go | 76 + models/rbacmodel/const.go | 6 + models/rbacmodel/group.go | 125 + models/rbacmodel/group_test.go | 85 + models/rbacmodel/policies.go | 112 + models/rbacmodel/policies_test.go | 59 + models/rbacmodel/resoure.go | 63 + models/rbacmodel/user_group.go | 81 + models/rbacmodel/user_group_test.go | 39 + models/repo.go | 22 + models/repository_test.go | 2 +- models/tag_test.go | 2 +- models/tree_test.go | 2 +- models/user_test.go | 11 +- models/wip_test.go | 2 +- testhelper/tf.go | 11 + utils/arr.go | 19 + utils/arr_test.go | 34 + utils/error.go | 13 + versionmgr/work_repo.go | 12 - 79 files changed, 9363 insertions(+), 3875 deletions(-) delete mode 100644 auth/basic_auth.go create mode 100644 auth/rbac/arn.go create mode 100644 auth/rbac/arn_test.go create mode 100644 auth/rbac/rbac.go create mode 100644 auth/rbac/rbac_test.go create mode 100644 auth/rbac/statements.go create mode 100644 auth/rbac/wildcard/match.go create mode 100644 auth/rbac/wildcard/match_test.go create mode 100644 controller/base_ctl.go create mode 100644 controller/group.go create mode 100644 controller/member.go create mode 100644 docs/authorization.md rename docs/{ => draws}/jiaozifs_aim.drawio (100%) rename docs/{ => draws}/jiaozifs_mind.drawio (100%) create mode 100644 docs/draws/rbac_role.drawio create mode 100644 docs/rbac.md create mode 100644 integrationtest/group_test.go create mode 100644 integrationtest/member_test.go create mode 100644 models/members.go create mode 100644 models/members_test.go create mode 100644 models/rbacmodel/actions.gen.go create mode 100644 models/rbacmodel/actions.go create mode 100644 models/rbacmodel/actions_test.go create mode 100644 models/rbacmodel/codegen/main.go create mode 100644 models/rbacmodel/const.go create mode 100644 models/rbacmodel/group.go create mode 100644 models/rbacmodel/group_test.go create mode 100644 models/rbacmodel/policies.go create mode 100644 models/rbacmodel/policies_test.go create mode 100644 models/rbacmodel/resoure.go create mode 100644 models/rbacmodel/user_group.go create mode 100644 models/rbacmodel/user_group_test.go create mode 100644 testhelper/tf.go create mode 100644 utils/error.go diff --git a/api/api_impl/impl.go b/api/api_impl/impl.go index 3a3a2c24..41f8794a 100644 --- a/api/api_impl/impl.go +++ b/api/api_impl/impl.go @@ -20,4 +20,7 @@ type APIController struct { controller.BranchController controller.MergeRequestController controller.AkSkController + + controller.GroupController + controller.MemberController } diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 5be23d25..7825cd71 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -168,6 +168,15 @@ type FullTreeEntry struct { UpdatedAt int64 `json:"updated_at"` } +// Group defines model for Group. +type Group struct { + CreatedAt int64 `json:"created_at"` + Id openapi_types.UUID `json:"id"` + Name string `json:"name"` + Policies []openapi_types.UUID `json:"policies"` + UpdatedAt int64 `json:"updated_at"` +} + // LoginConfig defines model for LoginConfig. type LoginConfig struct { // RBAC RBAC will remain enabled on GUI if "external". That only works @@ -198,6 +207,16 @@ type LoginConfig struct { // with an external auth service. type LoginConfigRBAC string +// Member defines model for Member. +type Member struct { + CreatedAt int64 `json:"created_at"` + GroupId openapi_types.UUID `json:"group_id"` + Id openapi_types.UUID `json:"id"` + RepoId openapi_types.UUID `json:"repo_id"` + UpdatedAt int64 `json:"updated_at"` + UserId openapi_types.UUID `json:"user_id"` +} + // MergeMergeRequest defines model for MergeMergeRequest. type MergeMergeRequest struct { // ConflictResolve use to record the resolution of the conflict, example({"b/a.txt":"left"}) @@ -399,16 +418,6 @@ type LoginJSONBody struct { Password string `json:"password"` } -// ListMergeRequestsParams defines parameters for ListMergeRequests. -type ListMergeRequestsParams struct { - // After return items after this value - After *PaginationInt64After `form:"after,omitempty" json:"after,omitempty"` - - // Amount how many items to return - Amount *PaginationAmount `form:"amount,omitempty" json:"amount,omitempty"` - State *int `form:"state,omitempty" json:"state,omitempty"` -} - // DeleteObjectParams defines parameters for DeleteObject. type DeleteObjectParams struct { // RefName branch/tag to the ref @@ -526,6 +535,33 @@ type GetEntriesInRefParams struct { Type RefType `form:"type" json:"type"` } +// RevokeMemberParams defines parameters for RevokeMember. +type RevokeMemberParams struct { + UserId openapi_types.UUID `form:"user_id" json:"user_id"` +} + +// UpdateMemberGroupParams defines parameters for UpdateMemberGroup. +type UpdateMemberGroupParams struct { + UserId openapi_types.UUID `form:"user_id" json:"user_id"` + GroupId openapi_types.UUID `form:"group_id" json:"group_id"` +} + +// InviteMemberParams defines parameters for InviteMember. +type InviteMemberParams struct { + UserId openapi_types.UUID `form:"user_id" json:"user_id"` + GroupId openapi_types.UUID `form:"group_id" json:"group_id"` +} + +// ListMergeRequestsParams defines parameters for ListMergeRequests. +type ListMergeRequestsParams struct { + // After return items after this value + After *PaginationInt64After `form:"after,omitempty" json:"after,omitempty"` + + // Amount how many items to return + Amount *PaginationAmount `form:"amount,omitempty" json:"amount,omitempty"` + State *int `form:"state,omitempty" json:"state,omitempty"` +} + // DeleteAkskParams defines parameters for DeleteAksk. type DeleteAkskParams struct { Id *openapi_types.UUID `form:"id,omitempty" json:"id,omitempty"` @@ -624,15 +660,6 @@ type RevertWipChangesParams struct { // LoginJSONRequestBody defines body for Login for application/json ContentType. type LoginJSONRequestBody LoginJSONBody -// UpdateMergeRequestJSONRequestBody defines body for UpdateMergeRequest for application/json ContentType. -type UpdateMergeRequestJSONRequestBody = UpdateMergeRequest - -// CreateMergeRequestJSONRequestBody defines body for CreateMergeRequest for application/json ContentType. -type CreateMergeRequestJSONRequestBody = CreateMergeRequest - -// MergeJSONRequestBody defines body for Merge for application/json ContentType. -type MergeJSONRequestBody = MergeMergeRequest - // UploadObjectMultipartRequestBody defines body for UploadObject for multipart/form-data ContentType. type UploadObjectMultipartRequestBody UploadObjectMultipartBody @@ -642,6 +669,15 @@ type UpdateRepositoryJSONRequestBody = UpdateRepository // CreateBranchJSONRequestBody defines body for CreateBranch for application/json ContentType. type CreateBranchJSONRequestBody = BranchCreation +// CreateMergeRequestJSONRequestBody defines body for CreateMergeRequest for application/json ContentType. +type CreateMergeRequestJSONRequestBody = CreateMergeRequest + +// UpdateMergeRequestJSONRequestBody defines body for UpdateMergeRequest for application/json ContentType. +type UpdateMergeRequestJSONRequestBody = UpdateMergeRequest + +// MergeJSONRequestBody defines body for Merge for application/json ContentType. +type MergeJSONRequestBody = MergeMergeRequest + // RegisterJSONRequestBody defines body for Register for application/json ContentType. type RegisterJSONRequestBody = UserRegisterInfo @@ -732,26 +768,8 @@ type ClientInterface interface { // Logout request Logout(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - // GetMergeRequest request - GetMergeRequest(ctx context.Context, owner string, repository string, mrSeq uint64, reqEditors ...RequestEditorFn) (*http.Response, error) - - // UpdateMergeRequestWithBody request with any body - UpdateMergeRequestWithBody(ctx context.Context, owner string, repository string, mrSeq uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - UpdateMergeRequest(ctx context.Context, owner string, repository string, mrSeq uint64, body UpdateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // ListMergeRequests request - ListMergeRequests(ctx context.Context, owner string, repository string, params *ListMergeRequestsParams, reqEditors ...RequestEditorFn) (*http.Response, error) - - // CreateMergeRequestWithBody request with any body - CreateMergeRequestWithBody(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - CreateMergeRequest(ctx context.Context, owner string, repository string, body CreateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // MergeWithBody request with any body - MergeWithBody(ctx context.Context, owner string, repository string, mrSeq uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - Merge(ctx context.Context, owner string, repository string, mrSeq uint64, body MergeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // ListRepoGroup request + ListRepoGroup(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) // DeleteObject request DeleteObject(ctx context.Context, owner string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -802,6 +820,39 @@ type ClientInterface interface { // GetEntriesInRef request GetEntriesInRef(ctx context.Context, owner string, repository string, params *GetEntriesInRefParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // RevokeMember request + RevokeMember(ctx context.Context, owner string, repository string, params *RevokeMemberParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UpdateMemberGroup request + UpdateMemberGroup(ctx context.Context, owner string, repository string, params *UpdateMemberGroupParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // InviteMember request + InviteMember(ctx context.Context, owner string, repository string, params *InviteMemberParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // ListMembers request + ListMembers(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // ListMergeRequests request + ListMergeRequests(ctx context.Context, owner string, repository string, params *ListMergeRequestsParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateMergeRequestWithBody request with any body + CreateMergeRequestWithBody(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateMergeRequest(ctx context.Context, owner string, repository string, body CreateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetMergeRequest request + GetMergeRequest(ctx context.Context, owner string, repository string, mrSeq uint64, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UpdateMergeRequestWithBody request with any body + UpdateMergeRequestWithBody(ctx context.Context, owner string, repository string, mrSeq uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + UpdateMergeRequest(ctx context.Context, owner string, repository string, mrSeq uint64, body UpdateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // MergeWithBody request with any body + MergeWithBody(ctx context.Context, owner string, repository string, mrSeq uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + Merge(ctx context.Context, owner string, repository string, mrSeq uint64, body MergeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetSetupState request GetSetupState(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -902,8 +953,8 @@ func (c *Client) Logout(ctx context.Context, reqEditors ...RequestEditorFn) (*ht return c.Client.Do(req) } -func (c *Client) GetMergeRequest(ctx context.Context, owner string, repository string, mrSeq uint64, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetMergeRequestRequest(c.Server, owner, repository, mrSeq) +func (c *Client) ListRepoGroup(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListRepoGroupRequest(c.Server) if err != nil { return nil, err } @@ -914,8 +965,8 @@ func (c *Client) GetMergeRequest(ctx context.Context, owner string, repository s return c.Client.Do(req) } -func (c *Client) UpdateMergeRequestWithBody(ctx context.Context, owner string, repository string, mrSeq uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewUpdateMergeRequestRequestWithBody(c.Server, owner, repository, mrSeq, contentType, body) +func (c *Client) DeleteObject(ctx context.Context, owner string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteObjectRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -926,8 +977,8 @@ func (c *Client) UpdateMergeRequestWithBody(ctx context.Context, owner string, r return c.Client.Do(req) } -func (c *Client) UpdateMergeRequest(ctx context.Context, owner string, repository string, mrSeq uint64, body UpdateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewUpdateMergeRequestRequest(c.Server, owner, repository, mrSeq, body) +func (c *Client) GetObject(ctx context.Context, owner string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetObjectRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -938,8 +989,8 @@ func (c *Client) UpdateMergeRequest(ctx context.Context, owner string, repositor return c.Client.Do(req) } -func (c *Client) ListMergeRequests(ctx context.Context, owner string, repository string, params *ListMergeRequestsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewListMergeRequestsRequest(c.Server, owner, repository, params) +func (c *Client) HeadObject(ctx context.Context, owner string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewHeadObjectRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -950,8 +1001,8 @@ func (c *Client) ListMergeRequests(ctx context.Context, owner string, repository return c.Client.Do(req) } -func (c *Client) CreateMergeRequestWithBody(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateMergeRequestRequestWithBody(c.Server, owner, repository, contentType, body) +func (c *Client) UploadObjectWithBody(ctx context.Context, owner string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUploadObjectRequestWithBody(c.Server, owner, repository, params, contentType, body) if err != nil { return nil, err } @@ -962,8 +1013,8 @@ func (c *Client) CreateMergeRequestWithBody(ctx context.Context, owner string, r return c.Client.Do(req) } -func (c *Client) CreateMergeRequest(ctx context.Context, owner string, repository string, body CreateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateMergeRequestRequest(c.Server, owner, repository, body) +func (c *Client) DeleteRepository(ctx context.Context, owner string, repository string, params *DeleteRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteRepositoryRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -974,8 +1025,8 @@ func (c *Client) CreateMergeRequest(ctx context.Context, owner string, repositor return c.Client.Do(req) } -func (c *Client) MergeWithBody(ctx context.Context, owner string, repository string, mrSeq uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewMergeRequestWithBody(c.Server, owner, repository, mrSeq, contentType, body) +func (c *Client) GetRepository(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetRepositoryRequest(c.Server, owner, repository) if err != nil { return nil, err } @@ -986,8 +1037,8 @@ func (c *Client) MergeWithBody(ctx context.Context, owner string, repository str return c.Client.Do(req) } -func (c *Client) Merge(ctx context.Context, owner string, repository string, mrSeq uint64, body MergeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewMergeRequest(c.Server, owner, repository, mrSeq, body) +func (c *Client) UpdateRepositoryWithBody(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateRepositoryRequestWithBody(c.Server, owner, repository, contentType, body) if err != nil { return nil, err } @@ -998,8 +1049,8 @@ func (c *Client) Merge(ctx context.Context, owner string, repository string, mrS return c.Client.Do(req) } -func (c *Client) DeleteObject(ctx context.Context, owner string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteObjectRequest(c.Server, owner, repository, params) +func (c *Client) UpdateRepository(ctx context.Context, owner string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateRepositoryRequest(c.Server, owner, repository, body) if err != nil { return nil, err } @@ -1010,8 +1061,8 @@ func (c *Client) DeleteObject(ctx context.Context, owner string, repository stri return c.Client.Do(req) } -func (c *Client) GetObject(ctx context.Context, owner string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetObjectRequest(c.Server, owner, repository, params) +func (c *Client) DeleteBranch(ctx context.Context, owner string, repository string, params *DeleteBranchParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteBranchRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -1022,8 +1073,8 @@ func (c *Client) GetObject(ctx context.Context, owner string, repository string, return c.Client.Do(req) } -func (c *Client) HeadObject(ctx context.Context, owner string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewHeadObjectRequest(c.Server, owner, repository, params) +func (c *Client) GetBranch(ctx context.Context, owner string, repository string, params *GetBranchParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetBranchRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -1034,8 +1085,8 @@ func (c *Client) HeadObject(ctx context.Context, owner string, repository string return c.Client.Do(req) } -func (c *Client) UploadObjectWithBody(ctx context.Context, owner string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewUploadObjectRequestWithBody(c.Server, owner, repository, params, contentType, body) +func (c *Client) CreateBranchWithBody(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateBranchRequestWithBody(c.Server, owner, repository, contentType, body) if err != nil { return nil, err } @@ -1046,8 +1097,8 @@ func (c *Client) UploadObjectWithBody(ctx context.Context, owner string, reposit return c.Client.Do(req) } -func (c *Client) DeleteRepository(ctx context.Context, owner string, repository string, params *DeleteRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteRepositoryRequest(c.Server, owner, repository, params) +func (c *Client) CreateBranch(ctx context.Context, owner string, repository string, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateBranchRequest(c.Server, owner, repository, body) if err != nil { return nil, err } @@ -1058,8 +1109,8 @@ func (c *Client) DeleteRepository(ctx context.Context, owner string, repository return c.Client.Do(req) } -func (c *Client) GetRepository(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetRepositoryRequest(c.Server, owner, repository) +func (c *Client) ListBranches(ctx context.Context, owner string, repository string, params *ListBranchesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListBranchesRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -1070,8 +1121,8 @@ func (c *Client) GetRepository(ctx context.Context, owner string, repository str return c.Client.Do(req) } -func (c *Client) UpdateRepositoryWithBody(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewUpdateRepositoryRequestWithBody(c.Server, owner, repository, contentType, body) +func (c *Client) GetCommitChanges(ctx context.Context, owner string, repository string, commitId string, params *GetCommitChangesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetCommitChangesRequest(c.Server, owner, repository, commitId, params) if err != nil { return nil, err } @@ -1082,8 +1133,8 @@ func (c *Client) UpdateRepositoryWithBody(ctx context.Context, owner string, rep return c.Client.Do(req) } -func (c *Client) UpdateRepository(ctx context.Context, owner string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewUpdateRepositoryRequest(c.Server, owner, repository, body) +func (c *Client) GetCommitsInRef(ctx context.Context, owner string, repository string, params *GetCommitsInRefParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetCommitsInRefRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -1094,8 +1145,8 @@ func (c *Client) UpdateRepository(ctx context.Context, owner string, repository return c.Client.Do(req) } -func (c *Client) DeleteBranch(ctx context.Context, owner string, repository string, params *DeleteBranchParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteBranchRequest(c.Server, owner, repository, params) +func (c *Client) CompareCommit(ctx context.Context, owner string, repository string, basehead string, params *CompareCommitParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCompareCommitRequest(c.Server, owner, repository, basehead, params) if err != nil { return nil, err } @@ -1106,8 +1157,8 @@ func (c *Client) DeleteBranch(ctx context.Context, owner string, repository stri return c.Client.Do(req) } -func (c *Client) GetBranch(ctx context.Context, owner string, repository string, params *GetBranchParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetBranchRequest(c.Server, owner, repository, params) +func (c *Client) GetEntriesInRef(ctx context.Context, owner string, repository string, params *GetEntriesInRefParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetEntriesInRefRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -1118,8 +1169,8 @@ func (c *Client) GetBranch(ctx context.Context, owner string, repository string, return c.Client.Do(req) } -func (c *Client) CreateBranchWithBody(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateBranchRequestWithBody(c.Server, owner, repository, contentType, body) +func (c *Client) RevokeMember(ctx context.Context, owner string, repository string, params *RevokeMemberParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRevokeMemberRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -1130,8 +1181,8 @@ func (c *Client) CreateBranchWithBody(ctx context.Context, owner string, reposit return c.Client.Do(req) } -func (c *Client) CreateBranch(ctx context.Context, owner string, repository string, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateBranchRequest(c.Server, owner, repository, body) +func (c *Client) UpdateMemberGroup(ctx context.Context, owner string, repository string, params *UpdateMemberGroupParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateMemberGroupRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -1142,8 +1193,8 @@ func (c *Client) CreateBranch(ctx context.Context, owner string, repository stri return c.Client.Do(req) } -func (c *Client) ListBranches(ctx context.Context, owner string, repository string, params *ListBranchesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewListBranchesRequest(c.Server, owner, repository, params) +func (c *Client) InviteMember(ctx context.Context, owner string, repository string, params *InviteMemberParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewInviteMemberRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -1154,8 +1205,8 @@ func (c *Client) ListBranches(ctx context.Context, owner string, repository stri return c.Client.Do(req) } -func (c *Client) GetCommitChanges(ctx context.Context, owner string, repository string, commitId string, params *GetCommitChangesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetCommitChangesRequest(c.Server, owner, repository, commitId, params) +func (c *Client) ListMembers(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListMembersRequest(c.Server, owner, repository) if err != nil { return nil, err } @@ -1166,8 +1217,8 @@ func (c *Client) GetCommitChanges(ctx context.Context, owner string, repository return c.Client.Do(req) } -func (c *Client) GetCommitsInRef(ctx context.Context, owner string, repository string, params *GetCommitsInRefParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetCommitsInRefRequest(c.Server, owner, repository, params) +func (c *Client) ListMergeRequests(ctx context.Context, owner string, repository string, params *ListMergeRequestsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListMergeRequestsRequest(c.Server, owner, repository, params) if err != nil { return nil, err } @@ -1178,8 +1229,8 @@ func (c *Client) GetCommitsInRef(ctx context.Context, owner string, repository s return c.Client.Do(req) } -func (c *Client) CompareCommit(ctx context.Context, owner string, repository string, basehead string, params *CompareCommitParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCompareCommitRequest(c.Server, owner, repository, basehead, params) +func (c *Client) CreateMergeRequestWithBody(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateMergeRequestRequestWithBody(c.Server, owner, repository, contentType, body) if err != nil { return nil, err } @@ -1190,8 +1241,68 @@ func (c *Client) CompareCommit(ctx context.Context, owner string, repository str return c.Client.Do(req) } -func (c *Client) GetEntriesInRef(ctx context.Context, owner string, repository string, params *GetEntriesInRefParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetEntriesInRefRequest(c.Server, owner, repository, params) +func (c *Client) CreateMergeRequest(ctx context.Context, owner string, repository string, body CreateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateMergeRequestRequest(c.Server, owner, repository, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetMergeRequest(ctx context.Context, owner string, repository string, mrSeq uint64, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetMergeRequestRequest(c.Server, owner, repository, mrSeq) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateMergeRequestWithBody(ctx context.Context, owner string, repository string, mrSeq uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateMergeRequestRequestWithBody(c.Server, owner, repository, mrSeq, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateMergeRequest(ctx context.Context, owner string, repository string, mrSeq uint64, body UpdateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateMergeRequestRequest(c.Server, owner, repository, mrSeq, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) MergeWithBody(ctx context.Context, owner string, repository string, mrSeq uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewMergeRequestWithBody(c.Server, owner, repository, mrSeq, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) Merge(ctx context.Context, owner string, repository string, mrSeq uint64, body MergeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewMergeRequest(c.Server, owner, repository, mrSeq, body) if err != nil { return nil, err } @@ -1533,42 +1644,21 @@ func NewLogoutRequest(server string) (*http.Request, error) { return req, nil } -// NewGetMergeRequestRequest generates requests for GetMergeRequest -func NewGetMergeRequestRequest(server string, owner string, repository string, mrSeq uint64) (*http.Request, error) { +// NewListRepoGroupRequest generates requests for ListRepoGroup +func NewListRepoGroupRequest(server string) (*http.Request, error) { var err error - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + serverURL, err := url.Parse(server) if err != nil { return nil, err } - var pathParam1 string + operationPath := fmt.Sprintf("/groups/repo") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) - if err != nil { - return nil, err - } - - var pathParam2 string - - pathParam2, err = runtime.StyleParamWithLocation("simple", false, "mrSeq", runtime.ParamLocationPath, mrSeq) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/mergequest/%s/%s/%s", pathParam0, pathParam1, pathParam2) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) + queryURL, err := serverURL.Parse(operationPath) if err != nil { return nil, err } @@ -1581,19 +1671,8 @@ func NewGetMergeRequestRequest(server string, owner string, repository string, m return req, nil } -// NewUpdateMergeRequestRequest calls the generic UpdateMergeRequest builder with application/json body -func NewUpdateMergeRequestRequest(server string, owner string, repository string, mrSeq uint64, body UpdateMergeRequestJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewUpdateMergeRequestRequestWithBody(server, owner, repository, mrSeq, "application/json", bodyReader) -} - -// NewUpdateMergeRequestRequestWithBody generates requests for UpdateMergeRequest with any type of body -func NewUpdateMergeRequestRequestWithBody(server string, owner string, repository string, mrSeq uint64, contentType string, body io.Reader) (*http.Request, error) { +// NewDeleteObjectRequest generates requests for DeleteObject +func NewDeleteObjectRequest(server string, owner string, repository string, params *DeleteObjectParams) (*http.Request, error) { var err error var pathParam0 string @@ -1610,19 +1689,12 @@ func NewUpdateMergeRequestRequestWithBody(server string, owner string, repositor return nil, err } - var pathParam2 string - - pathParam2, err = runtime.StyleParamWithLocation("simple", false, "mrSeq", runtime.ParamLocationPath, mrSeq) - if err != nil { - return nil, err - } - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/mergequest/%s/%s/%s", pathParam0, pathParam1, pathParam2) + operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1632,18 +1704,46 @@ func NewUpdateMergeRequestRequestWithBody(server string, owner string, repositor return nil, err } - req, err := http.NewRequest("POST", queryURL.String(), body) + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) if err != nil { return nil, err } - req.Header.Add("Content-Type", contentType) - return req, nil } -// NewListMergeRequestsRequest generates requests for ListMergeRequests -func NewListMergeRequestsRequest(server string, owner string, repository string, params *ListMergeRequestsParams) (*http.Request, error) { +// NewGetObjectRequest generates requests for GetObject +func NewGetObjectRequest(server string, owner string, repository string, params *GetObjectParams) (*http.Request, error) { var err error var pathParam0 string @@ -1665,7 +1765,7 @@ func NewListMergeRequestsRequest(server string, owner string, repository string, return nil, err } - operationPath := fmt.Sprintf("/mergerequest/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1678,52 +1778,40 @@ func NewListMergeRequestsRequest(server string, owner string, repository string, if params != nil { queryValues := queryURL.Query() - if params.After != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "after", runtime.ParamLocationQuery, *params.After); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "type", runtime.ParamLocationQuery, params.Type); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) } } - } - if params.Amount != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "amount", runtime.ParamLocationQuery, *params.Amount); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) } } - } - if params.State != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "state", runtime.ParamLocationQuery, *params.State); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) } } - } queryURL.RawQuery = queryValues.Encode() @@ -1734,22 +1822,26 @@ func NewListMergeRequestsRequest(server string, owner string, repository string, return nil, err } - return req, nil -} + if params != nil { + + if params.Range != nil { + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithLocation("simple", false, "Range", runtime.ParamLocationHeader, *params.Range) + if err != nil { + return nil, err + } + + req.Header.Set("Range", headerParam0) + } -// NewCreateMergeRequestRequest calls the generic CreateMergeRequest builder with application/json body -func NewCreateMergeRequestRequest(server string, owner string, repository string, body CreateMergeRequestJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err } - bodyReader = bytes.NewReader(buf) - return NewCreateMergeRequestRequestWithBody(server, owner, repository, "application/json", bodyReader) + + return req, nil } -// NewCreateMergeRequestRequestWithBody generates requests for CreateMergeRequest with any type of body -func NewCreateMergeRequestRequestWithBody(server string, owner string, repository string, contentType string, body io.Reader) (*http.Request, error) { +// NewHeadObjectRequest generates requests for HeadObject +func NewHeadObjectRequest(server string, owner string, repository string, params *HeadObjectParams) (*http.Request, error) { var err error var pathParam0 string @@ -1771,7 +1863,7 @@ func NewCreateMergeRequestRequestWithBody(server string, owner string, repositor return nil, err } - operationPath := fmt.Sprintf("/mergerequest/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1781,29 +1873,73 @@ func NewCreateMergeRequestRequestWithBody(server string, owner string, repositor return nil, err } - req, err := http.NewRequest("POST", queryURL.String(), body) + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "type", runtime.ParamLocationQuery, params.Type); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("HEAD", queryURL.String(), nil) if err != nil { return nil, err } - req.Header.Add("Content-Type", contentType) + if params != nil { - return req, nil -} + if params.Range != nil { + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithLocation("simple", false, "Range", runtime.ParamLocationHeader, *params.Range) + if err != nil { + return nil, err + } + + req.Header.Set("Range", headerParam0) + } -// NewMergeRequest calls the generic Merge builder with application/json body -func NewMergeRequest(server string, owner string, repository string, mrSeq uint64, body MergeJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err } - bodyReader = bytes.NewReader(buf) - return NewMergeRequestWithBody(server, owner, repository, mrSeq, "application/json", bodyReader) + + return req, nil } -// NewMergeRequestWithBody generates requests for Merge with any type of body -func NewMergeRequestWithBody(server string, owner string, repository string, mrSeq uint64, contentType string, body io.Reader) (*http.Request, error) { +// NewUploadObjectRequestWithBody generates requests for UploadObject with any type of body +func NewUploadObjectRequestWithBody(server string, owner string, repository string, params *UploadObjectParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -1820,62 +1956,12 @@ func NewMergeRequestWithBody(server string, owner string, repository string, mrS return nil, err } - var pathParam2 string - - pathParam2, err = runtime.StyleParamWithLocation("simple", false, "mrSeq", runtime.ParamLocationPath, mrSeq) - if err != nil { - return nil, err - } - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/mergerequest/%s/%s/%s/merge", pathParam0, pathParam1, pathParam2) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("POST", queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewDeleteObjectRequest generates requests for DeleteObject -func NewDeleteObjectRequest(server string, owner string, repository string, params *DeleteObjectParams) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) - if err != nil { - return nil, err - } - - var pathParam1 string - - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1915,16 +2001,18 @@ func NewDeleteObjectRequest(server string, owner string, repository string, para queryURL.RawQuery = queryValues.Encode() } - req, err := http.NewRequest("DELETE", queryURL.String(), nil) + req, err := http.NewRequest("POST", queryURL.String(), body) if err != nil { return nil, err } + req.Header.Add("Content-Type", contentType) + return req, nil } -// NewGetObjectRequest generates requests for GetObject -func NewGetObjectRequest(server string, owner string, repository string, params *GetObjectParams) (*http.Request, error) { +// NewDeleteRepositoryRequest generates requests for DeleteRepository +func NewDeleteRepositoryRequest(server string, owner string, repository string, params *DeleteRepositoryParams) (*http.Request, error) { var err error var pathParam0 string @@ -1946,7 +2034,7 @@ func NewGetObjectRequest(server string, owner string, repository string, params return nil, err } - operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/repos/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1959,70 +2047,35 @@ func NewGetObjectRequest(server string, owner string, repository string, params if params != nil { queryValues := queryURL.Query() - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "type", runtime.ParamLocationQuery, params.Type); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } + if params.IsCleanData != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "is_clean_data", runtime.ParamLocationQuery, *params.IsCleanData); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } - } - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } } queryURL.RawQuery = queryValues.Encode() } - req, err := http.NewRequest("GET", queryURL.String(), nil) + req, err := http.NewRequest("DELETE", queryURL.String(), nil) if err != nil { return nil, err } - if params != nil { - - if params.Range != nil { - var headerParam0 string - - headerParam0, err = runtime.StyleParamWithLocation("simple", false, "Range", runtime.ParamLocationHeader, *params.Range) - if err != nil { - return nil, err - } - - req.Header.Set("Range", headerParam0) - } - - } - return req, nil } -// NewHeadObjectRequest generates requests for HeadObject -func NewHeadObjectRequest(server string, owner string, repository string, params *HeadObjectParams) (*http.Request, error) { +// NewGetRepositoryRequest generates requests for GetRepository +func NewGetRepositoryRequest(server string, owner string, repository string) (*http.Request, error) { var err error var pathParam0 string @@ -2044,7 +2097,7 @@ func NewHeadObjectRequest(server string, owner string, repository string, params return nil, err } - operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/repos/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2054,73 +2107,27 @@ func NewHeadObjectRequest(server string, owner string, repository string, params return nil, err } - if params != nil { - queryValues := queryURL.Query() - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "type", runtime.ParamLocationQuery, params.Type); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - queryURL.RawQuery = queryValues.Encode() - } - - req, err := http.NewRequest("HEAD", queryURL.String(), nil) + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } - if params != nil { - - if params.Range != nil { - var headerParam0 string - - headerParam0, err = runtime.StyleParamWithLocation("simple", false, "Range", runtime.ParamLocationHeader, *params.Range) - if err != nil { - return nil, err - } - - req.Header.Set("Range", headerParam0) - } + return req, nil +} +// NewUpdateRepositoryRequest calls the generic UpdateRepository builder with application/json body +func NewUpdateRepositoryRequest(server string, owner string, repository string, body UpdateRepositoryJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err } - - return req, nil + bodyReader = bytes.NewReader(buf) + return NewUpdateRepositoryRequestWithBody(server, owner, repository, "application/json", bodyReader) } -// NewUploadObjectRequestWithBody generates requests for UploadObject with any type of body -func NewUploadObjectRequestWithBody(server string, owner string, repository string, params *UploadObjectParams, contentType string, body io.Reader) (*http.Request, error) { +// NewUpdateRepositoryRequestWithBody generates requests for UpdateRepository with any type of body +func NewUpdateRepositoryRequestWithBody(server string, owner string, repository string, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -2142,7 +2149,7 @@ func NewUploadObjectRequestWithBody(server string, owner string, repository stri return nil, err } - operationPath := fmt.Sprintf("/object/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/repos/%s/%s", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2152,36 +2159,6 @@ func NewUploadObjectRequestWithBody(server string, owner string, repository stri return nil, err } - if params != nil { - queryValues := queryURL.Query() - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - queryURL.RawQuery = queryValues.Encode() - } - req, err := http.NewRequest("POST", queryURL.String(), body) if err != nil { return nil, err @@ -2192,8 +2169,8 @@ func NewUploadObjectRequestWithBody(server string, owner string, repository stri return req, nil } -// NewDeleteRepositoryRequest generates requests for DeleteRepository -func NewDeleteRepositoryRequest(server string, owner string, repository string, params *DeleteRepositoryParams) (*http.Request, error) { +// NewDeleteBranchRequest generates requests for DeleteBranch +func NewDeleteBranchRequest(server string, owner string, repository string, params *DeleteBranchParams) (*http.Request, error) { var err error var pathParam0 string @@ -2215,7 +2192,7 @@ func NewDeleteRepositoryRequest(server string, owner string, repository string, return nil, err } - operationPath := fmt.Sprintf("/repos/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/repos/%s/%s/branch", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2228,20 +2205,16 @@ func NewDeleteRepositoryRequest(server string, owner string, repository string, if params != nil { queryValues := queryURL.Query() - if params.IsCleanData != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "is_clean_data", runtime.ParamLocationQuery, *params.IsCleanData); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) } } - } queryURL.RawQuery = queryValues.Encode() @@ -2255,8 +2228,8 @@ func NewDeleteRepositoryRequest(server string, owner string, repository string, return req, nil } -// NewGetRepositoryRequest generates requests for GetRepository -func NewGetRepositoryRequest(server string, owner string, repository string) (*http.Request, error) { +// NewGetBranchRequest generates requests for GetBranch +func NewGetBranchRequest(server string, owner string, repository string, params *GetBranchParams) (*http.Request, error) { var err error var pathParam0 string @@ -2278,7 +2251,7 @@ func NewGetRepositoryRequest(server string, owner string, repository string) (*h return nil, err } - operationPath := fmt.Sprintf("/repos/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/repos/%s/%s/branch", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2288,162 +2261,8 @@ func NewGetRepositoryRequest(server string, owner string, repository string) (*h return nil, err } - req, err := http.NewRequest("GET", queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewUpdateRepositoryRequest calls the generic UpdateRepository builder with application/json body -func NewUpdateRepositoryRequest(server string, owner string, repository string, body UpdateRepositoryJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewUpdateRepositoryRequestWithBody(server, owner, repository, "application/json", bodyReader) -} - -// NewUpdateRepositoryRequestWithBody generates requests for UpdateRepository with any type of body -func NewUpdateRepositoryRequestWithBody(server string, owner string, repository string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) - if err != nil { - return nil, err - } - - var pathParam1 string - - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/repos/%s/%s", pathParam0, pathParam1) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("POST", queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewDeleteBranchRequest generates requests for DeleteBranch -func NewDeleteBranchRequest(server string, owner string, repository string, params *DeleteBranchParams) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) - if err != nil { - return nil, err - } - - var pathParam1 string - - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/repos/%s/%s/branch", pathParam0, pathParam1) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - if params != nil { - queryValues := queryURL.Query() - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - queryURL.RawQuery = queryValues.Encode() - } - - req, err := http.NewRequest("DELETE", queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewGetBranchRequest generates requests for GetBranch -func NewGetBranchRequest(server string, owner string, repository string, params *GetBranchParams) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) - if err != nil { - return nil, err - } - - var pathParam1 string - - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/repos/%s/%s/branch", pathParam0, pathParam1) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - if params != nil { - queryValues := queryURL.Query() + if params != nil { + queryValues := queryURL.Query() if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { return nil, err @@ -2943,16 +2762,30 @@ func NewGetEntriesInRefRequest(server string, owner string, repository string, p return req, nil } -// NewGetSetupStateRequest generates requests for GetSetupState -func NewGetSetupStateRequest(server string) (*http.Request, error) { +// NewRevokeMemberRequest generates requests for RevokeMember +func NewRevokeMemberRequest(server string, owner string, repository string, params *RevokeMemberParams) (*http.Request, error) { var err error + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/setup") + operationPath := fmt.Sprintf("/repos/%s/%s/member", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2962,7 +2795,25 @@ func NewGetSetupStateRequest(server string) (*http.Request, error) { return nil, err } - req, err := http.NewRequest("GET", queryURL.String(), nil) + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "user_id", runtime.ParamLocationQuery, params.UserId); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) if err != nil { return nil, err } @@ -2970,16 +2821,30 @@ func NewGetSetupStateRequest(server string) (*http.Request, error) { return req, nil } -// NewDeleteAkskRequest generates requests for DeleteAksk -func NewDeleteAkskRequest(server string, params *DeleteAkskParams) (*http.Request, error) { +// NewUpdateMemberGroupRequest generates requests for UpdateMemberGroup +func NewUpdateMemberGroupRequest(server string, owner string, repository string, params *UpdateMemberGroupParams) (*http.Request, error) { var err error + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/users/aksk") + operationPath := fmt.Sprintf("/repos/%s/%s/member", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -2992,42 +2857,34 @@ func NewDeleteAkskRequest(server string, params *DeleteAkskParams) (*http.Reques if params != nil { queryValues := queryURL.Query() - if params.Id != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "id", runtime.ParamLocationQuery, *params.Id); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "user_id", runtime.ParamLocationQuery, params.UserId); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) } } - } - if params.AccessKey != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "access_key", runtime.ParamLocationQuery, *params.AccessKey); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "group_id", runtime.ParamLocationQuery, params.GroupId); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) } } - } queryURL.RawQuery = queryValues.Encode() } - req, err := http.NewRequest("DELETE", queryURL.String(), nil) + req, err := http.NewRequest("POST", queryURL.String(), nil) if err != nil { return nil, err } @@ -3035,81 +2892,30 @@ func NewDeleteAkskRequest(server string, params *DeleteAkskParams) (*http.Reques return req, nil } -// NewGetAkskRequest generates requests for GetAksk -func NewGetAkskRequest(server string, params *GetAkskParams) (*http.Request, error) { +// NewInviteMemberRequest generates requests for InviteMember +func NewInviteMemberRequest(server string, owner string, repository string, params *InviteMemberParams) (*http.Request, error) { var err error - serverURL, err := url.Parse(server) + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/users/aksk") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } + var pathParam1 string - queryURL, err := serverURL.Parse(operationPath) + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) if err != nil { return nil, err } - if params != nil { - queryValues := queryURL.Query() - - if params.Id != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "id", runtime.ParamLocationQuery, *params.Id); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - if params.AccessKey != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "access_key", runtime.ParamLocationQuery, *params.AccessKey); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - queryURL.RawQuery = queryValues.Encode() - } - - req, err := http.NewRequest("GET", queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewCreateAkskRequest generates requests for CreateAksk -func NewCreateAkskRequest(server string, params *CreateAkskParams) (*http.Request, error) { - var err error - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/users/aksk") + operationPath := fmt.Sprintf("/repos/%s/%s/member/invite", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -3122,20 +2928,28 @@ func NewCreateAkskRequest(server string, params *CreateAkskParams) (*http.Reques if params != nil { queryValues := queryURL.Query() - if params.Description != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "description", runtime.ParamLocationQuery, *params.Description); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "user_id", runtime.ParamLocationQuery, params.UserId); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) } } + } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "group_id", runtime.ParamLocationQuery, params.GroupId); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } } queryURL.RawQuery = queryValues.Encode() @@ -3149,81 +2963,30 @@ func NewCreateAkskRequest(server string, params *CreateAkskParams) (*http.Reques return req, nil } -// NewListAksksRequest generates requests for ListAksks -func NewListAksksRequest(server string, params *ListAksksParams) (*http.Request, error) { +// NewListMembersRequest generates requests for ListMembers +func NewListMembersRequest(server string, owner string, repository string) (*http.Request, error) { var err error - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/users/aksks") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } + var pathParam0 string - queryURL, err := serverURL.Parse(operationPath) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } - if params != nil { - queryValues := queryURL.Query() - - if params.After != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "after", runtime.ParamLocationQuery, *params.After); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - if params.Amount != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "amount", runtime.ParamLocationQuery, *params.Amount); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - queryURL.RawQuery = queryValues.Encode() - } + var pathParam1 string - req, err := http.NewRequest("GET", queryURL.String(), nil) + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) if err != nil { return nil, err } - return req, nil -} - -// NewRefreshTokenRequest generates requests for RefreshToken -func NewRefreshTokenRequest(server string) (*http.Request, error) { - var err error - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/users/refreshtoken") + operationPath := fmt.Sprintf("/repos/%s/%s/members", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -3241,56 +3004,30 @@ func NewRefreshTokenRequest(server string) (*http.Request, error) { return req, nil } -// NewRegisterRequest calls the generic Register builder with application/json body -func NewRegisterRequest(server string, body RegisterJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewRegisterRequestWithBody(server, "application/json", bodyReader) -} - -// NewRegisterRequestWithBody generates requests for Register with any type of body -func NewRegisterRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { +// NewListMergeRequestsRequest generates requests for ListMergeRequests +func NewListMergeRequestsRequest(server string, owner string, repository string, params *ListMergeRequestsParams) (*http.Request, error) { var err error - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/users/register") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } + var pathParam0 string - queryURL, err := serverURL.Parse(operationPath) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } - req, err := http.NewRequest("POST", queryURL.String(), body) + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) if err != nil { return nil, err } - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewListRepositoryOfAuthenticatedUserRequest generates requests for ListRepositoryOfAuthenticatedUser -func NewListRepositoryOfAuthenticatedUserRequest(server string, params *ListRepositoryOfAuthenticatedUserParams) (*http.Request, error) { - var err error - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/users/repos") + operationPath := fmt.Sprintf("/repos/%s/%s/mergerequest", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -3303,9 +3040,9 @@ func NewListRepositoryOfAuthenticatedUserRequest(server string, params *ListRepo if params != nil { queryValues := queryURL.Query() - if params.Prefix != nil { + if params.After != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "prefix", runtime.ParamLocationQuery, *params.Prefix); err != nil { + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "after", runtime.ParamLocationQuery, *params.After); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -3319,9 +3056,9 @@ func NewListRepositoryOfAuthenticatedUserRequest(server string, params *ListRepo } - if params.After != nil { + if params.Amount != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "after", runtime.ParamLocationQuery, *params.After); err != nil { + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "amount", runtime.ParamLocationQuery, *params.Amount); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -3335,9 +3072,9 @@ func NewListRepositoryOfAuthenticatedUserRequest(server string, params *ListRepo } - if params.Amount != nil { + if params.State != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "amount", runtime.ParamLocationQuery, *params.Amount); err != nil { + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "state", runtime.ParamLocationQuery, *params.State); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -3362,27 +3099,41 @@ func NewListRepositoryOfAuthenticatedUserRequest(server string, params *ListRepo return req, nil } -// NewCreateRepositoryRequest calls the generic CreateRepository builder with application/json body -func NewCreateRepositoryRequest(server string, body CreateRepositoryJSONRequestBody) (*http.Request, error) { +// NewCreateMergeRequestRequest calls the generic CreateMergeRequest builder with application/json body +func NewCreateMergeRequestRequest(server string, owner string, repository string, body CreateMergeRequestJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewCreateRepositoryRequestWithBody(server, "application/json", bodyReader) + return NewCreateMergeRequestRequestWithBody(server, owner, repository, "application/json", bodyReader) } -// NewCreateRepositoryRequestWithBody generates requests for CreateRepository with any type of body -func NewCreateRepositoryRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { +// NewCreateMergeRequestRequestWithBody generates requests for CreateMergeRequest with any type of body +func NewCreateMergeRequestRequestWithBody(server string, owner string, repository string, contentType string, body io.Reader) (*http.Request, error) { var err error + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/users/repos") + operationPath := fmt.Sprintf("/repos/%s/%s/mergerequest", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -3402,40 +3153,27 @@ func NewCreateRepositoryRequestWithBody(server string, contentType string, body return req, nil } -// NewGetUserInfoRequest generates requests for GetUserInfo -func NewGetUserInfoRequest(server string) (*http.Request, error) { +// NewGetMergeRequestRequest generates requests for GetMergeRequest +func NewGetMergeRequestRequest(server string, owner string, repository string, mrSeq uint64) (*http.Request, error) { var err error - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/users/user") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } + var pathParam0 string - queryURL, err := serverURL.Parse(operationPath) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } - req, err := http.NewRequest("GET", queryURL.String(), nil) + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) if err != nil { return nil, err } - return req, nil -} - -// NewListRepositoryRequest generates requests for ListRepository -func NewListRepositoryRequest(server string, owner string, params *ListRepositoryParams) (*http.Request, error) { - var err error - - var pathParam0 string + var pathParam2 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "mrSeq", runtime.ParamLocationPath, mrSeq) if err != nil { return nil, err } @@ -3445,7 +3183,7 @@ func NewListRepositoryRequest(server string, owner string, params *ListRepositor return nil, err } - operationPath := fmt.Sprintf("/users/%s/repos", pathParam0) + operationPath := fmt.Sprintf("/repos/%s/%s/mergerequest/%s", pathParam0, pathParam1, pathParam2) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -3455,97 +3193,27 @@ func NewListRepositoryRequest(server string, owner string, params *ListRepositor return nil, err } - if params != nil { - queryValues := queryURL.Query() - - if params.Prefix != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "prefix", runtime.ParamLocationQuery, *params.Prefix); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - if params.After != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "after", runtime.ParamLocationQuery, *params.After); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - if params.Amount != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "amount", runtime.ParamLocationQuery, *params.Amount); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - queryURL.RawQuery = queryValues.Encode() - } - - req, err := http.NewRequest("GET", queryURL.String(), nil) - if err != nil { - return nil, err - } + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } return req, nil } -// NewGetVersionRequest generates requests for GetVersion -func NewGetVersionRequest(server string) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/version") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("GET", queryURL.String(), nil) +// NewUpdateMergeRequestRequest calls the generic UpdateMergeRequest builder with application/json body +func NewUpdateMergeRequestRequest(server string, owner string, repository string, mrSeq uint64, body UpdateMergeRequestJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) if err != nil { return nil, err } - - return req, nil + bodyReader = bytes.NewReader(buf) + return NewUpdateMergeRequestRequestWithBody(server, owner, repository, mrSeq, "application/json", bodyReader) } -// NewDeleteWipRequest generates requests for DeleteWip -func NewDeleteWipRequest(server string, owner string, repository string, params *DeleteWipParams) (*http.Request, error) { +// NewUpdateMergeRequestRequestWithBody generates requests for UpdateMergeRequest with any type of body +func NewUpdateMergeRequestRequestWithBody(server string, owner string, repository string, mrSeq uint64, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -3562,61 +3230,9 @@ func NewDeleteWipRequest(server string, owner string, repository string, params return nil, err } - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/wip/%s/%s", pathParam0, pathParam1) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - if params != nil { - queryValues := queryURL.Query() - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - queryURL.RawQuery = queryValues.Encode() - } - - req, err := http.NewRequest("DELETE", queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewGetWipRequest generates requests for GetWip -func NewGetWipRequest(server string, owner string, repository string, params *GetWipParams) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) - if err != nil { - return nil, err - } - - var pathParam1 string + var pathParam2 string - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "mrSeq", runtime.ParamLocationPath, mrSeq) if err != nil { return nil, err } @@ -3626,7 +3242,7 @@ func NewGetWipRequest(server string, owner string, repository string, params *Ge return nil, err } - operationPath := fmt.Sprintf("/wip/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/repos/%s/%s/mergerequest/%s", pathParam0, pathParam1, pathParam2) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -3636,45 +3252,29 @@ func NewGetWipRequest(server string, owner string, repository string, params *Ge return nil, err } - if params != nil { - queryValues := queryURL.Query() - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - queryURL.RawQuery = queryValues.Encode() - } - - req, err := http.NewRequest("GET", queryURL.String(), nil) + req, err := http.NewRequest("POST", queryURL.String(), body) if err != nil { return nil, err } + req.Header.Add("Content-Type", contentType) + return req, nil } -// NewUpdateWipRequest calls the generic UpdateWip builder with application/json body -func NewUpdateWipRequest(server string, owner string, repository string, params *UpdateWipParams, body UpdateWipJSONRequestBody) (*http.Request, error) { +// NewMergeRequest calls the generic Merge builder with application/json body +func NewMergeRequest(server string, owner string, repository string, mrSeq uint64, body MergeJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewUpdateWipRequestWithBody(server, owner, repository, params, "application/json", bodyReader) + return NewMergeRequestWithBody(server, owner, repository, mrSeq, "application/json", bodyReader) } -// NewUpdateWipRequestWithBody generates requests for UpdateWip with any type of body -func NewUpdateWipRequestWithBody(server string, owner string, repository string, params *UpdateWipParams, contentType string, body io.Reader) (*http.Request, error) { +// NewMergeRequestWithBody generates requests for Merge with any type of body +func NewMergeRequestWithBody(server string, owner string, repository string, mrSeq uint64, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -3691,12 +3291,19 @@ func NewUpdateWipRequestWithBody(server string, owner string, repository string, return nil, err } + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "mrSeq", runtime.ParamLocationPath, mrSeq) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/wip/%s/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/repos/%s/%s/mergerequest/%s/merge", pathParam0, pathParam1, pathParam2) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -3706,24 +3313,6 @@ func NewUpdateWipRequestWithBody(server string, owner string, repository string, return nil, err } - if params != nil { - queryValues := queryURL.Query() - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - queryURL.RawQuery = queryValues.Encode() - } - req, err := http.NewRequest("POST", queryURL.String(), body) if err != nil { return nil, err @@ -3734,30 +3323,43 @@ func NewUpdateWipRequestWithBody(server string, owner string, repository string, return req, nil } -// NewGetWipChangesRequest generates requests for GetWipChanges -func NewGetWipChangesRequest(server string, owner string, repository string, params *GetWipChangesParams) (*http.Request, error) { +// NewGetSetupStateRequest generates requests for GetSetupState +func NewGetSetupStateRequest(server string) (*http.Request, error) { var err error - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + serverURL, err := url.Parse(server) if err != nil { return nil, err } - var pathParam1 string + operationPath := fmt.Sprintf("/setup") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } + return req, nil +} + +// NewDeleteAkskRequest generates requests for DeleteAksk +func NewDeleteAkskRequest(server string, params *DeleteAkskParams) (*http.Request, error) { + var err error + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/wip/%s/%s/changes", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/users/aksk") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -3770,21 +3372,25 @@ func NewGetWipChangesRequest(server string, owner string, repository string, par if params != nil { queryValues := queryURL.Query() - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if params.Id != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "id", runtime.ParamLocationQuery, *params.Id); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } + } - if params.Path != nil { + if params.AccessKey != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "access_key", runtime.ParamLocationQuery, *params.AccessKey); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -3801,7 +3407,7 @@ func NewGetWipChangesRequest(server string, owner string, repository string, par queryURL.RawQuery = queryValues.Encode() } - req, err := http.NewRequest("GET", queryURL.String(), nil) + req, err := http.NewRequest("DELETE", queryURL.String(), nil) if err != nil { return nil, err } @@ -3809,30 +3415,81 @@ func NewGetWipChangesRequest(server string, owner string, repository string, par return req, nil } -// NewCommitWipRequest generates requests for CommitWip -func NewCommitWipRequest(server string, owner string, repository string, params *CommitWipParams) (*http.Request, error) { +// NewGetAkskRequest generates requests for GetAksk +func NewGetAkskRequest(server string, params *GetAkskParams) (*http.Request, error) { var err error - var pathParam0 string + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + operationPath := fmt.Sprintf("/users/aksk") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) if err != nil { return nil, err } - var pathParam1 string + if params != nil { + queryValues := queryURL.Query() - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if params.Id != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "id", runtime.ParamLocationQuery, *params.Id); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.AccessKey != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "access_key", runtime.ParamLocationQuery, *params.AccessKey); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } + return req, nil +} + +// NewCreateAkskRequest generates requests for CreateAksk +func NewCreateAkskRequest(server string, params *CreateAkskParams) (*http.Request, error) { + var err error + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/wip/%s/%s/commit", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/users/aksk") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -3845,28 +3502,20 @@ func NewCommitWipRequest(server string, owner string, repository string, params if params != nil { queryValues := queryURL.Query() - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "msg", runtime.ParamLocationQuery, params.Msg); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } + if params.Description != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "description", runtime.ParamLocationQuery, *params.Description); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } + } queryURL.RawQuery = queryValues.Encode() @@ -3880,30 +3529,81 @@ func NewCommitWipRequest(server string, owner string, repository string, params return req, nil } -// NewListWipRequest generates requests for ListWip -func NewListWipRequest(server string, owner string, repository string) (*http.Request, error) { +// NewListAksksRequest generates requests for ListAksks +func NewListAksksRequest(server string, params *ListAksksParams) (*http.Request, error) { var err error - var pathParam0 string + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + operationPath := fmt.Sprintf("/users/aksks") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) if err != nil { return nil, err } - var pathParam1 string + if params != nil { + queryValues := queryURL.Query() - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if params.After != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "after", runtime.ParamLocationQuery, *params.After); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Amount != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "amount", runtime.ParamLocationQuery, *params.Amount); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } + return req, nil +} + +// NewRefreshTokenRequest generates requests for RefreshToken +func NewRefreshTokenRequest(server string) (*http.Request, error) { + var err error + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/wip/%s/%s/list", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/users/refreshtoken") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -3921,30 +3621,56 @@ func NewListWipRequest(server string, owner string, repository string) (*http.Re return req, nil } -// NewRevertWipChangesRequest generates requests for RevertWipChanges -func NewRevertWipChangesRequest(server string, owner string, repository string, params *RevertWipChangesParams) (*http.Request, error) { - var err error +// NewRegisterRequest calls the generic Register builder with application/json body +func NewRegisterRequest(server string, body RegisterJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewRegisterRequestWithBody(server, "application/json", bodyReader) +} - var pathParam0 string +// NewRegisterRequestWithBody generates requests for Register with any type of body +func NewRegisterRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + serverURL, err := url.Parse(server) if err != nil { return nil, err } - var pathParam1 string + operationPath := fmt.Sprintf("/users/register") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) if err != nil { return nil, err } + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewListRepositoryOfAuthenticatedUserRequest generates requests for ListRepositoryOfAuthenticatedUser +func NewListRepositoryOfAuthenticatedUserRequest(server string, params *ListRepositoryOfAuthenticatedUserParams) (*http.Request, error) { + var err error + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/wip/%s/%s/revert", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/users/repos") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -3957,21 +3683,41 @@ func NewRevertWipChangesRequest(server string, owner string, repository string, if params != nil { queryValues := queryURL.Query() - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if params.Prefix != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "prefix", runtime.ParamLocationQuery, *params.Prefix); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.After != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "after", runtime.ParamLocationQuery, *params.After); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } + } - if params.PathPrefix != nil { + if params.Amount != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "pathPrefix", runtime.ParamLocationQuery, *params.PathPrefix); err != nil { + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "amount", runtime.ParamLocationQuery, *params.Amount); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -3988,7 +3734,7 @@ func NewRevertWipChangesRequest(server string, owner string, repository string, queryURL.RawQuery = queryValues.Encode() } - req, err := http.NewRequest("POST", queryURL.String(), nil) + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } @@ -3996,740 +3742,848 @@ func NewRevertWipChangesRequest(server string, owner string, repository string, return req, nil } -func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { - for _, r := range c.RequestEditors { - if err := r(ctx, req); err != nil { - return err - } - } - for _, r := range additionalEditors { - if err := r(ctx, req); err != nil { - return err - } +// NewCreateRepositoryRequest calls the generic CreateRepository builder with application/json body +func NewCreateRepositoryRequest(server string, body CreateRepositoryJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err } - return nil + bodyReader = bytes.NewReader(buf) + return NewCreateRepositoryRequestWithBody(server, "application/json", bodyReader) } -// ClientWithResponses builds on ClientInterface to offer response payloads -type ClientWithResponses struct { - ClientInterface -} +// NewCreateRepositoryRequestWithBody generates requests for CreateRepository with any type of body +func NewCreateRepositoryRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error -// NewClientWithResponses creates a new ClientWithResponses, which wraps -// Client with return type handling -func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { - client, err := NewClient(server, opts...) + serverURL, err := url.Parse(server) if err != nil { return nil, err } - return &ClientWithResponses{client}, nil -} -// WithBaseURL overrides the baseURL. -func WithBaseURL(baseURL string) ClientOption { - return func(c *Client) error { - newBaseURL, err := url.Parse(baseURL) - if err != nil { - return err - } - c.Server = newBaseURL.String() - return nil + operationPath := fmt.Sprintf("/users/repos") + if operationPath[0] == '/' { + operationPath = "." + operationPath } -} - -// ClientWithResponsesInterface is the interface specification for the client with responses above. -type ClientWithResponsesInterface interface { - // LoginWithBodyWithResponse request with any body - LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) - LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } - // LogoutWithResponse request - LogoutWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*LogoutResponse, error) + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } - // GetMergeRequestWithResponse request - GetMergeRequestWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, reqEditors ...RequestEditorFn) (*GetMergeRequestResponse, error) + req.Header.Add("Content-Type", contentType) - // UpdateMergeRequestWithBodyWithResponse request with any body - UpdateMergeRequestWithBodyWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateMergeRequestResponse, error) + return req, nil +} - UpdateMergeRequestWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, body UpdateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateMergeRequestResponse, error) +// NewGetUserInfoRequest generates requests for GetUserInfo +func NewGetUserInfoRequest(server string) (*http.Request, error) { + var err error - // ListMergeRequestsWithResponse request - ListMergeRequestsWithResponse(ctx context.Context, owner string, repository string, params *ListMergeRequestsParams, reqEditors ...RequestEditorFn) (*ListMergeRequestsResponse, error) + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } - // CreateMergeRequestWithBodyWithResponse request with any body - CreateMergeRequestWithBodyWithResponse(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateMergeRequestResponse, error) + operationPath := fmt.Sprintf("/users/user") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } - CreateMergeRequestWithResponse(ctx context.Context, owner string, repository string, body CreateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateMergeRequestResponse, error) + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } - // MergeWithBodyWithResponse request with any body - MergeWithBodyWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MergeResponse, error) + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } - MergeWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, body MergeJSONRequestBody, reqEditors ...RequestEditorFn) (*MergeResponse, error) + return req, nil +} - // DeleteObjectWithResponse request - DeleteObjectWithResponse(ctx context.Context, owner string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) +// NewListRepositoryRequest generates requests for ListRepository +func NewListRepositoryRequest(server string, owner string, params *ListRepositoryParams) (*http.Request, error) { + var err error - // GetObjectWithResponse request - GetObjectWithResponse(ctx context.Context, owner string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*GetObjectResponse, error) + var pathParam0 string - // HeadObjectWithResponse request - HeadObjectWithResponse(ctx context.Context, owner string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*HeadObjectResponse, error) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err + } - // UploadObjectWithBodyWithResponse request with any body - UploadObjectWithBodyWithResponse(ctx context.Context, owner string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } - // DeleteRepositoryWithResponse request - DeleteRepositoryWithResponse(ctx context.Context, owner string, repository string, params *DeleteRepositoryParams, reqEditors ...RequestEditorFn) (*DeleteRepositoryResponse, error) + operationPath := fmt.Sprintf("/users/%s/repos", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } - // GetRepositoryWithResponse request - GetRepositoryWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*GetRepositoryResponse, error) + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } - // UpdateRepositoryWithBodyWithResponse request with any body - UpdateRepositoryWithBodyWithResponse(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) + if params != nil { + queryValues := queryURL.Query() - UpdateRepositoryWithResponse(ctx context.Context, owner string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) + if params.Prefix != nil { - // DeleteBranchWithResponse request - DeleteBranchWithResponse(ctx context.Context, owner string, repository string, params *DeleteBranchParams, reqEditors ...RequestEditorFn) (*DeleteBranchResponse, error) + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "prefix", runtime.ParamLocationQuery, *params.Prefix); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } - // GetBranchWithResponse request - GetBranchWithResponse(ctx context.Context, owner string, repository string, params *GetBranchParams, reqEditors ...RequestEditorFn) (*GetBranchResponse, error) + } - // CreateBranchWithBodyWithResponse request with any body - CreateBranchWithBodyWithResponse(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) + if params.After != nil { - CreateBranchWithResponse(ctx context.Context, owner string, repository string, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "after", runtime.ParamLocationQuery, *params.After); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } - // ListBranchesWithResponse request - ListBranchesWithResponse(ctx context.Context, owner string, repository string, params *ListBranchesParams, reqEditors ...RequestEditorFn) (*ListBranchesResponse, error) + } - // GetCommitChangesWithResponse request - GetCommitChangesWithResponse(ctx context.Context, owner string, repository string, commitId string, params *GetCommitChangesParams, reqEditors ...RequestEditorFn) (*GetCommitChangesResponse, error) + if params.Amount != nil { - // GetCommitsInRefWithResponse request - GetCommitsInRefWithResponse(ctx context.Context, owner string, repository string, params *GetCommitsInRefParams, reqEditors ...RequestEditorFn) (*GetCommitsInRefResponse, error) + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "amount", runtime.ParamLocationQuery, *params.Amount); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } - // CompareCommitWithResponse request - CompareCommitWithResponse(ctx context.Context, owner string, repository string, basehead string, params *CompareCommitParams, reqEditors ...RequestEditorFn) (*CompareCommitResponse, error) + } - // GetEntriesInRefWithResponse request - GetEntriesInRefWithResponse(ctx context.Context, owner string, repository string, params *GetEntriesInRefParams, reqEditors ...RequestEditorFn) (*GetEntriesInRefResponse, error) + queryURL.RawQuery = queryValues.Encode() + } - // GetSetupStateWithResponse request - GetSetupStateWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetSetupStateResponse, error) + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } - // DeleteAkskWithResponse request - DeleteAkskWithResponse(ctx context.Context, params *DeleteAkskParams, reqEditors ...RequestEditorFn) (*DeleteAkskResponse, error) + return req, nil +} - // GetAkskWithResponse request - GetAkskWithResponse(ctx context.Context, params *GetAkskParams, reqEditors ...RequestEditorFn) (*GetAkskResponse, error) +// NewGetVersionRequest generates requests for GetVersion +func NewGetVersionRequest(server string) (*http.Request, error) { + var err error - // CreateAkskWithResponse request - CreateAkskWithResponse(ctx context.Context, params *CreateAkskParams, reqEditors ...RequestEditorFn) (*CreateAkskResponse, error) + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } - // ListAksksWithResponse request - ListAksksWithResponse(ctx context.Context, params *ListAksksParams, reqEditors ...RequestEditorFn) (*ListAksksResponse, error) + operationPath := fmt.Sprintf("/version") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } - // RefreshTokenWithResponse request - RefreshTokenWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*RefreshTokenResponse, error) + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } - // RegisterWithBodyWithResponse request with any body - RegisterWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterResponse, error) + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } - RegisterWithResponse(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterResponse, error) + return req, nil +} - // ListRepositoryOfAuthenticatedUserWithResponse request - ListRepositoryOfAuthenticatedUserWithResponse(ctx context.Context, params *ListRepositoryOfAuthenticatedUserParams, reqEditors ...RequestEditorFn) (*ListRepositoryOfAuthenticatedUserResponse, error) +// NewDeleteWipRequest generates requests for DeleteWip +func NewDeleteWipRequest(server string, owner string, repository string, params *DeleteWipParams) (*http.Request, error) { + var err error - // CreateRepositoryWithBodyWithResponse request with any body - CreateRepositoryWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateRepositoryResponse, error) + var pathParam0 string - CreateRepositoryWithResponse(ctx context.Context, body CreateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateRepositoryResponse, error) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err + } - // GetUserInfoWithResponse request - GetUserInfoWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetUserInfoResponse, error) + var pathParam1 string - // ListRepositoryWithResponse request - ListRepositoryWithResponse(ctx context.Context, owner string, params *ListRepositoryParams, reqEditors ...RequestEditorFn) (*ListRepositoryResponse, error) + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } - // GetVersionWithResponse request - GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } - // DeleteWipWithResponse request - DeleteWipWithResponse(ctx context.Context, owner string, repository string, params *DeleteWipParams, reqEditors ...RequestEditorFn) (*DeleteWipResponse, error) - - // GetWipWithResponse request - GetWipWithResponse(ctx context.Context, owner string, repository string, params *GetWipParams, reqEditors ...RequestEditorFn) (*GetWipResponse, error) + operationPath := fmt.Sprintf("/wip/%s/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } - // UpdateWipWithBodyWithResponse request with any body - UpdateWipWithBodyWithResponse(ctx context.Context, owner string, repository string, params *UpdateWipParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateWipResponse, error) + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } - UpdateWipWithResponse(ctx context.Context, owner string, repository string, params *UpdateWipParams, body UpdateWipJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateWipResponse, error) + if params != nil { + queryValues := queryURL.Query() - // GetWipChangesWithResponse request - GetWipChangesWithResponse(ctx context.Context, owner string, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*GetWipChangesResponse, error) + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } - // CommitWipWithResponse request - CommitWipWithResponse(ctx context.Context, owner string, repository string, params *CommitWipParams, reqEditors ...RequestEditorFn) (*CommitWipResponse, error) + queryURL.RawQuery = queryValues.Encode() + } - // ListWipWithResponse request - ListWipWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*ListWipResponse, error) + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } - // RevertWipChangesWithResponse request - RevertWipChangesWithResponse(ctx context.Context, owner string, repository string, params *RevertWipChangesParams, reqEditors ...RequestEditorFn) (*RevertWipChangesResponse, error) + return req, nil } -type LoginResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *AuthenticationToken -} +// NewGetWipRequest generates requests for GetWip +func NewGetWipRequest(server string, owner string, repository string, params *GetWipParams) (*http.Request, error) { + var err error -// Status returns HTTPResponse.Status -func (r LoginResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err } - return http.StatusText(0) -} -// StatusCode returns HTTPResponse.StatusCode -func (r LoginResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err } - return 0 -} -type LogoutResponse struct { - Body []byte - HTTPResponse *http.Response -} + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } -// Status returns HTTPResponse.Status -func (r LogoutResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status + operationPath := fmt.Sprintf("/wip/%s/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath } - return http.StatusText(0) -} -// StatusCode returns HTTPResponse.StatusCode -func (r LogoutResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err } - return 0 -} -type GetMergeRequestResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *MergeRequestFullState -} + if params != nil { + queryValues := queryURL.Query() -// Status returns HTTPResponse.Status -func (r GetMergeRequestResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() } - return http.StatusText(0) -} -// StatusCode returns HTTPResponse.StatusCode -func (r GetMergeRequestResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err } - return 0 -} -type UpdateMergeRequestResponse struct { - Body []byte - HTTPResponse *http.Response + return req, nil } -// Status returns HTTPResponse.Status -func (r UpdateMergeRequestResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status +// NewUpdateWipRequest calls the generic UpdateWip builder with application/json body +func NewUpdateWipRequest(server string, owner string, repository string, params *UpdateWipParams, body UpdateWipJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err } - return http.StatusText(0) + bodyReader = bytes.NewReader(buf) + return NewUpdateWipRequestWithBody(server, owner, repository, params, "application/json", bodyReader) } -// StatusCode returns HTTPResponse.StatusCode -func (r UpdateMergeRequestResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} +// NewUpdateWipRequestWithBody generates requests for UpdateWip with any type of body +func NewUpdateWipRequestWithBody(server string, owner string, repository string, params *UpdateWipParams, contentType string, body io.Reader) (*http.Request, error) { + var err error -type ListMergeRequestsResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *MergeRequestList -} + var pathParam0 string -// Status returns HTTPResponse.Status -func (r ListMergeRequestsResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err } - return http.StatusText(0) -} -// StatusCode returns HTTPResponse.StatusCode -func (r ListMergeRequestsResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err } - return 0 -} -type CreateMergeRequestResponse struct { - Body []byte - HTTPResponse *http.Response - JSON201 *MergeRequest -} + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } -// Status returns HTTPResponse.Status -func (r CreateMergeRequestResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status + operationPath := fmt.Sprintf("/wip/%s/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath } - return http.StatusText(0) -} -// StatusCode returns HTTPResponse.StatusCode -func (r CreateMergeRequestResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err } - return 0 -} -type MergeResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *[]Commit -} + if params != nil { + queryValues := queryURL.Query() -// Status returns HTTPResponse.Status -func (r MergeResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() } - return http.StatusText(0) -} -// StatusCode returns HTTPResponse.StatusCode -func (r MergeResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err } - return 0 -} -type DeleteObjectResponse struct { - Body []byte - HTTPResponse *http.Response + req.Header.Add("Content-Type", contentType) + + return req, nil } -// Status returns HTTPResponse.Status -func (r DeleteObjectResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} +// NewGetWipChangesRequest generates requests for GetWipChanges +func NewGetWipChangesRequest(server string, owner string, repository string, params *GetWipChangesParams) (*http.Request, error) { + var err error -// StatusCode returns HTTPResponse.StatusCode -func (r DeleteObjectResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err } - return 0 -} -type GetObjectResponse struct { - Body []byte - HTTPResponse *http.Response -} + var pathParam1 string -// Status returns HTTPResponse.Status -func (r GetObjectResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err } - return http.StatusText(0) -} -// StatusCode returns HTTPResponse.StatusCode -func (r GetObjectResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + serverURL, err := url.Parse(server) + if err != nil { + return nil, err } - return 0 -} - -type HeadObjectResponse struct { - Body []byte - HTTPResponse *http.Response -} -// Status returns HTTPResponse.Status -func (r HeadObjectResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status + operationPath := fmt.Sprintf("/wip/%s/%s/changes", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath } - return http.StatusText(0) -} -// StatusCode returns HTTPResponse.StatusCode -func (r HeadObjectResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err } - return 0 -} -type UploadObjectResponse struct { - Body []byte - HTTPResponse *http.Response - JSON201 *ObjectStats -} + if params != nil { + queryValues := queryURL.Query() -// Status returns HTTPResponse.Status -func (r UploadObjectResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } -// StatusCode returns HTTPResponse.StatusCode -func (r UploadObjectResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} + if params.Path != nil { -type DeleteRepositoryResponse struct { - Body []byte - HTTPResponse *http.Response -} + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } -// Status returns HTTPResponse.Status -func (r DeleteRepositoryResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status + } + + queryURL.RawQuery = queryValues.Encode() } - return http.StatusText(0) -} -// StatusCode returns HTTPResponse.StatusCode -func (r DeleteRepositoryResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err } - return 0 -} -type GetRepositoryResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *Repository + return req, nil } -// Status returns HTTPResponse.Status -func (r GetRepositoryResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} +// NewCommitWipRequest generates requests for CommitWip +func NewCommitWipRequest(server string, owner string, repository string, params *CommitWipParams) (*http.Request, error) { + var err error -// StatusCode returns HTTPResponse.StatusCode -func (r GetRepositoryResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err } - return 0 -} -type UpdateRepositoryResponse struct { - Body []byte - HTTPResponse *http.Response -} + var pathParam1 string -// Status returns HTTPResponse.Status -func (r UpdateRepositoryResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err } - return http.StatusText(0) -} -// StatusCode returns HTTPResponse.StatusCode -func (r UpdateRepositoryResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + serverURL, err := url.Parse(server) + if err != nil { + return nil, err } - return 0 -} - -type DeleteBranchResponse struct { - Body []byte - HTTPResponse *http.Response -} -// Status returns HTTPResponse.Status -func (r DeleteBranchResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status + operationPath := fmt.Sprintf("/wip/%s/%s/commit", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath } - return http.StatusText(0) -} -// StatusCode returns HTTPResponse.StatusCode -func (r DeleteBranchResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err } - return 0 -} -type GetBranchResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *Branch -} + if params != nil { + queryValues := queryURL.Query() -// Status returns HTTPResponse.Status -func (r GetBranchResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "msg", runtime.ParamLocationQuery, params.Msg); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() } - return http.StatusText(0) -} -// StatusCode returns HTTPResponse.StatusCode -func (r GetBranchResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + req, err := http.NewRequest("POST", queryURL.String(), nil) + if err != nil { + return nil, err } - return 0 -} -type CreateBranchResponse struct { - Body []byte - HTTPResponse *http.Response - JSON201 *BranchCreation + return req, nil } -// Status returns HTTPResponse.Status -func (r CreateBranchResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} +// NewListWipRequest generates requests for ListWip +func NewListWipRequest(server string, owner string, repository string) (*http.Request, error) { + var err error -// StatusCode returns HTTPResponse.StatusCode -func (r CreateBranchResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err } - return 0 -} -type ListBranchesResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *BranchList -} + var pathParam1 string -// Status returns HTTPResponse.Status -func (r ListBranchesResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err } - return http.StatusText(0) -} -// StatusCode returns HTTPResponse.StatusCode -func (r ListBranchesResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + serverURL, err := url.Parse(server) + if err != nil { + return nil, err } - return 0 -} -type GetCommitChangesResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *[]Change -} + operationPath := fmt.Sprintf("/wip/%s/%s/list", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } -// Status returns HTTPResponse.Status -func (r GetCommitChangesResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err } - return http.StatusText(0) -} -// StatusCode returns HTTPResponse.StatusCode -func (r GetCommitChangesResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err } - return 0 -} -type GetCommitsInRefResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *[]Commit + return req, nil } -// Status returns HTTPResponse.Status -func (r GetCommitsInRefResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} +// NewRevertWipChangesRequest generates requests for RevertWipChanges +func NewRevertWipChangesRequest(server string, owner string, repository string, params *RevertWipChangesParams) (*http.Request, error) { + var err error -// StatusCode returns HTTPResponse.StatusCode -func (r GetCommitsInRefResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err } - return 0 -} -type CompareCommitResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *[]Change -} + var pathParam1 string -// Status returns HTTPResponse.Status -func (r CompareCommitResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err } - return http.StatusText(0) -} -// StatusCode returns HTTPResponse.StatusCode -func (r CompareCommitResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + serverURL, err := url.Parse(server) + if err != nil { + return nil, err } - return 0 -} - -type GetEntriesInRefResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *[]FullTreeEntry -} -// Status returns HTTPResponse.Status -func (r GetEntriesInRefResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status + operationPath := fmt.Sprintf("/wip/%s/%s/revert", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath } - return http.StatusText(0) -} -// StatusCode returns HTTPResponse.StatusCode -func (r GetEntriesInRefResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err } - return 0 -} -type GetSetupStateResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *SetupState -} + if params != nil { + queryValues := queryURL.Query() -// Status returns HTTPResponse.Status -func (r GetSetupStateResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + if params.PathPrefix != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "pathPrefix", runtime.ParamLocationQuery, *params.PathPrefix); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() } - return http.StatusText(0) -} -// StatusCode returns HTTPResponse.StatusCode -func (r GetSetupStateResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + req, err := http.NewRequest("POST", queryURL.String(), nil) + if err != nil { + return nil, err } - return 0 -} -type DeleteAkskResponse struct { - Body []byte - HTTPResponse *http.Response + return req, nil } -// Status returns HTTPResponse.Status -func (r DeleteAkskResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r DeleteAkskResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } } - return 0 + return nil } -type GetAkskResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *Aksk +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface } -// Status returns HTTPResponse.Status -func (r GetAkskResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err } - return http.StatusText(0) + return &ClientWithResponses{client}, nil } -// StatusCode returns HTTPResponse.StatusCode -func (r GetAkskResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil } - return 0 } -type CreateAkskResponse struct { +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // LoginWithBodyWithResponse request with any body + LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) + + LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) + + // LogoutWithResponse request + LogoutWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*LogoutResponse, error) + + // ListRepoGroupWithResponse request + ListRepoGroupWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListRepoGroupResponse, error) + + // DeleteObjectWithResponse request + DeleteObjectWithResponse(ctx context.Context, owner string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) + + // GetObjectWithResponse request + GetObjectWithResponse(ctx context.Context, owner string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*GetObjectResponse, error) + + // HeadObjectWithResponse request + HeadObjectWithResponse(ctx context.Context, owner string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*HeadObjectResponse, error) + + // UploadObjectWithBodyWithResponse request with any body + UploadObjectWithBodyWithResponse(ctx context.Context, owner string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) + + // DeleteRepositoryWithResponse request + DeleteRepositoryWithResponse(ctx context.Context, owner string, repository string, params *DeleteRepositoryParams, reqEditors ...RequestEditorFn) (*DeleteRepositoryResponse, error) + + // GetRepositoryWithResponse request + GetRepositoryWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*GetRepositoryResponse, error) + + // UpdateRepositoryWithBodyWithResponse request with any body + UpdateRepositoryWithBodyWithResponse(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) + + UpdateRepositoryWithResponse(ctx context.Context, owner string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) + + // DeleteBranchWithResponse request + DeleteBranchWithResponse(ctx context.Context, owner string, repository string, params *DeleteBranchParams, reqEditors ...RequestEditorFn) (*DeleteBranchResponse, error) + + // GetBranchWithResponse request + GetBranchWithResponse(ctx context.Context, owner string, repository string, params *GetBranchParams, reqEditors ...RequestEditorFn) (*GetBranchResponse, error) + + // CreateBranchWithBodyWithResponse request with any body + CreateBranchWithBodyWithResponse(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) + + CreateBranchWithResponse(ctx context.Context, owner string, repository string, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) + + // ListBranchesWithResponse request + ListBranchesWithResponse(ctx context.Context, owner string, repository string, params *ListBranchesParams, reqEditors ...RequestEditorFn) (*ListBranchesResponse, error) + + // GetCommitChangesWithResponse request + GetCommitChangesWithResponse(ctx context.Context, owner string, repository string, commitId string, params *GetCommitChangesParams, reqEditors ...RequestEditorFn) (*GetCommitChangesResponse, error) + + // GetCommitsInRefWithResponse request + GetCommitsInRefWithResponse(ctx context.Context, owner string, repository string, params *GetCommitsInRefParams, reqEditors ...RequestEditorFn) (*GetCommitsInRefResponse, error) + + // CompareCommitWithResponse request + CompareCommitWithResponse(ctx context.Context, owner string, repository string, basehead string, params *CompareCommitParams, reqEditors ...RequestEditorFn) (*CompareCommitResponse, error) + + // GetEntriesInRefWithResponse request + GetEntriesInRefWithResponse(ctx context.Context, owner string, repository string, params *GetEntriesInRefParams, reqEditors ...RequestEditorFn) (*GetEntriesInRefResponse, error) + + // RevokeMemberWithResponse request + RevokeMemberWithResponse(ctx context.Context, owner string, repository string, params *RevokeMemberParams, reqEditors ...RequestEditorFn) (*RevokeMemberResponse, error) + + // UpdateMemberGroupWithResponse request + UpdateMemberGroupWithResponse(ctx context.Context, owner string, repository string, params *UpdateMemberGroupParams, reqEditors ...RequestEditorFn) (*UpdateMemberGroupResponse, error) + + // InviteMemberWithResponse request + InviteMemberWithResponse(ctx context.Context, owner string, repository string, params *InviteMemberParams, reqEditors ...RequestEditorFn) (*InviteMemberResponse, error) + + // ListMembersWithResponse request + ListMembersWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*ListMembersResponse, error) + + // ListMergeRequestsWithResponse request + ListMergeRequestsWithResponse(ctx context.Context, owner string, repository string, params *ListMergeRequestsParams, reqEditors ...RequestEditorFn) (*ListMergeRequestsResponse, error) + + // CreateMergeRequestWithBodyWithResponse request with any body + CreateMergeRequestWithBodyWithResponse(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateMergeRequestResponse, error) + + CreateMergeRequestWithResponse(ctx context.Context, owner string, repository string, body CreateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateMergeRequestResponse, error) + + // GetMergeRequestWithResponse request + GetMergeRequestWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, reqEditors ...RequestEditorFn) (*GetMergeRequestResponse, error) + + // UpdateMergeRequestWithBodyWithResponse request with any body + UpdateMergeRequestWithBodyWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateMergeRequestResponse, error) + + UpdateMergeRequestWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, body UpdateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateMergeRequestResponse, error) + + // MergeWithBodyWithResponse request with any body + MergeWithBodyWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MergeResponse, error) + + MergeWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, body MergeJSONRequestBody, reqEditors ...RequestEditorFn) (*MergeResponse, error) + + // GetSetupStateWithResponse request + GetSetupStateWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetSetupStateResponse, error) + + // DeleteAkskWithResponse request + DeleteAkskWithResponse(ctx context.Context, params *DeleteAkskParams, reqEditors ...RequestEditorFn) (*DeleteAkskResponse, error) + + // GetAkskWithResponse request + GetAkskWithResponse(ctx context.Context, params *GetAkskParams, reqEditors ...RequestEditorFn) (*GetAkskResponse, error) + + // CreateAkskWithResponse request + CreateAkskWithResponse(ctx context.Context, params *CreateAkskParams, reqEditors ...RequestEditorFn) (*CreateAkskResponse, error) + + // ListAksksWithResponse request + ListAksksWithResponse(ctx context.Context, params *ListAksksParams, reqEditors ...RequestEditorFn) (*ListAksksResponse, error) + + // RefreshTokenWithResponse request + RefreshTokenWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*RefreshTokenResponse, error) + + // RegisterWithBodyWithResponse request with any body + RegisterWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterResponse, error) + + RegisterWithResponse(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterResponse, error) + + // ListRepositoryOfAuthenticatedUserWithResponse request + ListRepositoryOfAuthenticatedUserWithResponse(ctx context.Context, params *ListRepositoryOfAuthenticatedUserParams, reqEditors ...RequestEditorFn) (*ListRepositoryOfAuthenticatedUserResponse, error) + + // CreateRepositoryWithBodyWithResponse request with any body + CreateRepositoryWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateRepositoryResponse, error) + + CreateRepositoryWithResponse(ctx context.Context, body CreateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateRepositoryResponse, error) + + // GetUserInfoWithResponse request + GetUserInfoWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetUserInfoResponse, error) + + // ListRepositoryWithResponse request + ListRepositoryWithResponse(ctx context.Context, owner string, params *ListRepositoryParams, reqEditors ...RequestEditorFn) (*ListRepositoryResponse, error) + + // GetVersionWithResponse request + GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) + + // DeleteWipWithResponse request + DeleteWipWithResponse(ctx context.Context, owner string, repository string, params *DeleteWipParams, reqEditors ...RequestEditorFn) (*DeleteWipResponse, error) + + // GetWipWithResponse request + GetWipWithResponse(ctx context.Context, owner string, repository string, params *GetWipParams, reqEditors ...RequestEditorFn) (*GetWipResponse, error) + + // UpdateWipWithBodyWithResponse request with any body + UpdateWipWithBodyWithResponse(ctx context.Context, owner string, repository string, params *UpdateWipParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateWipResponse, error) + + UpdateWipWithResponse(ctx context.Context, owner string, repository string, params *UpdateWipParams, body UpdateWipJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateWipResponse, error) + + // GetWipChangesWithResponse request + GetWipChangesWithResponse(ctx context.Context, owner string, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*GetWipChangesResponse, error) + + // CommitWipWithResponse request + CommitWipWithResponse(ctx context.Context, owner string, repository string, params *CommitWipParams, reqEditors ...RequestEditorFn) (*CommitWipResponse, error) + + // ListWipWithResponse request + ListWipWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*ListWipResponse, error) + + // RevertWipChangesWithResponse request + RevertWipChangesWithResponse(ctx context.Context, owner string, repository string, params *RevertWipChangesParams, reqEditors ...RequestEditorFn) (*RevertWipChangesResponse, error) +} + +type LoginResponse struct { Body []byte HTTPResponse *http.Response - JSON201 *Aksk + JSON200 *AuthenticationToken } // Status returns HTTPResponse.Status -func (r CreateAkskResponse) Status() string { +func (r LoginResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -4737,21 +4591,20 @@ func (r CreateAkskResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r CreateAkskResponse) StatusCode() int { +func (r LoginResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type ListAksksResponse struct { +type LogoutResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *AkskList } // Status returns HTTPResponse.Status -func (r ListAksksResponse) Status() string { +func (r LogoutResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -4759,21 +4612,21 @@ func (r ListAksksResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r ListAksksResponse) StatusCode() int { +func (r LogoutResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type RefreshTokenResponse struct { +type ListRepoGroupResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *AuthenticationToken + JSON200 *[]Group } // Status returns HTTPResponse.Status -func (r RefreshTokenResponse) Status() string { +func (r ListRepoGroupResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -4781,20 +4634,20 @@ func (r RefreshTokenResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r RefreshTokenResponse) StatusCode() int { +func (r ListRepoGroupResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type RegisterResponse struct { +type DeleteObjectResponse struct { Body []byte HTTPResponse *http.Response } // Status returns HTTPResponse.Status -func (r RegisterResponse) Status() string { +func (r DeleteObjectResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -4802,21 +4655,20 @@ func (r RegisterResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r RegisterResponse) StatusCode() int { +func (r DeleteObjectResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type ListRepositoryOfAuthenticatedUserResponse struct { +type GetObjectResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *RepositoryList } // Status returns HTTPResponse.Status -func (r ListRepositoryOfAuthenticatedUserResponse) Status() string { +func (r GetObjectResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -4824,21 +4676,20 @@ func (r ListRepositoryOfAuthenticatedUserResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r ListRepositoryOfAuthenticatedUserResponse) StatusCode() int { +func (r GetObjectResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type CreateRepositoryResponse struct { +type HeadObjectResponse struct { Body []byte HTTPResponse *http.Response - JSON201 *Repository } // Status returns HTTPResponse.Status -func (r CreateRepositoryResponse) Status() string { +func (r HeadObjectResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -4846,21 +4697,21 @@ func (r CreateRepositoryResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r CreateRepositoryResponse) StatusCode() int { +func (r HeadObjectResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetUserInfoResponse struct { +type UploadObjectResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *UserInfo + JSON201 *ObjectStats } // Status returns HTTPResponse.Status -func (r GetUserInfoResponse) Status() string { +func (r UploadObjectResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -4868,21 +4719,20 @@ func (r GetUserInfoResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetUserInfoResponse) StatusCode() int { +func (r UploadObjectResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type ListRepositoryResponse struct { +type DeleteRepositoryResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *RepositoryList } // Status returns HTTPResponse.Status -func (r ListRepositoryResponse) Status() string { +func (r DeleteRepositoryResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -4890,21 +4740,21 @@ func (r ListRepositoryResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r ListRepositoryResponse) StatusCode() int { +func (r DeleteRepositoryResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetVersionResponse struct { +type GetRepositoryResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *VersionResult + JSON200 *Repository } // Status returns HTTPResponse.Status -func (r GetVersionResponse) Status() string { +func (r GetRepositoryResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -4912,20 +4762,20 @@ func (r GetVersionResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetVersionResponse) StatusCode() int { +func (r GetRepositoryResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type DeleteWipResponse struct { +type UpdateRepositoryResponse struct { Body []byte HTTPResponse *http.Response } // Status returns HTTPResponse.Status -func (r DeleteWipResponse) Status() string { +func (r UpdateRepositoryResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -4933,21 +4783,20 @@ func (r DeleteWipResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r DeleteWipResponse) StatusCode() int { +func (r UpdateRepositoryResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetWipResponse struct { +type DeleteBranchResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *Wip } // Status returns HTTPResponse.Status -func (r GetWipResponse) Status() string { +func (r DeleteBranchResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -4955,20 +4804,21 @@ func (r GetWipResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetWipResponse) StatusCode() int { +func (r DeleteBranchResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type UpdateWipResponse struct { +type GetBranchResponse struct { Body []byte HTTPResponse *http.Response + JSON200 *Branch } // Status returns HTTPResponse.Status -func (r UpdateWipResponse) Status() string { +func (r GetBranchResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -4976,21 +4826,21 @@ func (r UpdateWipResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r UpdateWipResponse) StatusCode() int { +func (r GetBranchResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetWipChangesResponse struct { +type CreateBranchResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *[]Change + JSON201 *BranchCreation } // Status returns HTTPResponse.Status -func (r GetWipChangesResponse) Status() string { +func (r CreateBranchResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -4998,21 +4848,21 @@ func (r GetWipChangesResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetWipChangesResponse) StatusCode() int { +func (r CreateBranchResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type CommitWipResponse struct { +type ListBranchesResponse struct { Body []byte HTTPResponse *http.Response - JSON201 *Wip + JSON200 *BranchList } // Status returns HTTPResponse.Status -func (r CommitWipResponse) Status() string { +func (r ListBranchesResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -5020,21 +4870,21 @@ func (r CommitWipResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r CommitWipResponse) StatusCode() int { +func (r ListBranchesResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type ListWipResponse struct { +type GetCommitChangesResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *[]Wip + JSON200 *[]Change } // Status returns HTTPResponse.Status -func (r ListWipResponse) Status() string { +func (r GetCommitChangesResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -5042,20 +4892,21 @@ func (r ListWipResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r ListWipResponse) StatusCode() int { +func (r GetCommitChangesResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type RevertWipChangesResponse struct { +type GetCommitsInRefResponse struct { Body []byte HTTPResponse *http.Response + JSON200 *[]Commit } // Status returns HTTPResponse.Status -func (r RevertWipChangesResponse) Status() string { +func (r GetCommitsInRefResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -5063,1124 +4914,1167 @@ func (r RevertWipChangesResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r RevertWipChangesResponse) StatusCode() int { +func (r GetCommitsInRefResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -// LoginWithBodyWithResponse request with arbitrary body returning *LoginResponse -func (c *ClientWithResponses) LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) { - rsp, err := c.LoginWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseLoginResponse(rsp) -} - -func (c *ClientWithResponses) LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) { - rsp, err := c.Login(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseLoginResponse(rsp) +type CompareCommitResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]Change } -// LogoutWithResponse request returning *LogoutResponse -func (c *ClientWithResponses) LogoutWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*LogoutResponse, error) { - rsp, err := c.Logout(ctx, reqEditors...) - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r CompareCommitResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } - return ParseLogoutResponse(rsp) + return http.StatusText(0) } -// GetMergeRequestWithResponse request returning *GetMergeRequestResponse -func (c *ClientWithResponses) GetMergeRequestWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, reqEditors ...RequestEditorFn) (*GetMergeRequestResponse, error) { - rsp, err := c.GetMergeRequest(ctx, owner, repository, mrSeq, reqEditors...) - if err != nil { - return nil, err +// StatusCode returns HTTPResponse.StatusCode +func (r CompareCommitResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } - return ParseGetMergeRequestResponse(rsp) + return 0 } -// UpdateMergeRequestWithBodyWithResponse request with arbitrary body returning *UpdateMergeRequestResponse -func (c *ClientWithResponses) UpdateMergeRequestWithBodyWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateMergeRequestResponse, error) { - rsp, err := c.UpdateMergeRequestWithBody(ctx, owner, repository, mrSeq, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseUpdateMergeRequestResponse(rsp) +type GetEntriesInRefResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]FullTreeEntry } -func (c *ClientWithResponses) UpdateMergeRequestWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, body UpdateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateMergeRequestResponse, error) { - rsp, err := c.UpdateMergeRequest(ctx, owner, repository, mrSeq, body, reqEditors...) - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r GetEntriesInRefResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } - return ParseUpdateMergeRequestResponse(rsp) + return http.StatusText(0) } -// ListMergeRequestsWithResponse request returning *ListMergeRequestsResponse -func (c *ClientWithResponses) ListMergeRequestsWithResponse(ctx context.Context, owner string, repository string, params *ListMergeRequestsParams, reqEditors ...RequestEditorFn) (*ListMergeRequestsResponse, error) { - rsp, err := c.ListMergeRequests(ctx, owner, repository, params, reqEditors...) - if err != nil { - return nil, err +// StatusCode returns HTTPResponse.StatusCode +func (r GetEntriesInRefResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } - return ParseListMergeRequestsResponse(rsp) + return 0 } -// CreateMergeRequestWithBodyWithResponse request with arbitrary body returning *CreateMergeRequestResponse -func (c *ClientWithResponses) CreateMergeRequestWithBodyWithResponse(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateMergeRequestResponse, error) { - rsp, err := c.CreateMergeRequestWithBody(ctx, owner, repository, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseCreateMergeRequestResponse(rsp) +type RevokeMemberResponse struct { + Body []byte + HTTPResponse *http.Response } -func (c *ClientWithResponses) CreateMergeRequestWithResponse(ctx context.Context, owner string, repository string, body CreateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateMergeRequestResponse, error) { - rsp, err := c.CreateMergeRequest(ctx, owner, repository, body, reqEditors...) - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r RevokeMemberResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } - return ParseCreateMergeRequestResponse(rsp) + return http.StatusText(0) } -// MergeWithBodyWithResponse request with arbitrary body returning *MergeResponse -func (c *ClientWithResponses) MergeWithBodyWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MergeResponse, error) { - rsp, err := c.MergeWithBody(ctx, owner, repository, mrSeq, contentType, body, reqEditors...) - if err != nil { - return nil, err +// StatusCode returns HTTPResponse.StatusCode +func (r RevokeMemberResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } - return ParseMergeResponse(rsp) + return 0 } -func (c *ClientWithResponses) MergeWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, body MergeJSONRequestBody, reqEditors ...RequestEditorFn) (*MergeResponse, error) { - rsp, err := c.Merge(ctx, owner, repository, mrSeq, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseMergeResponse(rsp) +type UpdateMemberGroupResponse struct { + Body []byte + HTTPResponse *http.Response } -// DeleteObjectWithResponse request returning *DeleteObjectResponse -func (c *ClientWithResponses) DeleteObjectWithResponse(ctx context.Context, owner string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) { - rsp, err := c.DeleteObject(ctx, owner, repository, params, reqEditors...) - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r UpdateMemberGroupResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } - return ParseDeleteObjectResponse(rsp) + return http.StatusText(0) } -// GetObjectWithResponse request returning *GetObjectResponse -func (c *ClientWithResponses) GetObjectWithResponse(ctx context.Context, owner string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*GetObjectResponse, error) { - rsp, err := c.GetObject(ctx, owner, repository, params, reqEditors...) - if err != nil { - return nil, err +// StatusCode returns HTTPResponse.StatusCode +func (r UpdateMemberGroupResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } - return ParseGetObjectResponse(rsp) + return 0 } -// HeadObjectWithResponse request returning *HeadObjectResponse -func (c *ClientWithResponses) HeadObjectWithResponse(ctx context.Context, owner string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*HeadObjectResponse, error) { - rsp, err := c.HeadObject(ctx, owner, repository, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseHeadObjectResponse(rsp) +type InviteMemberResponse struct { + Body []byte + HTTPResponse *http.Response } -// UploadObjectWithBodyWithResponse request with arbitrary body returning *UploadObjectResponse -func (c *ClientWithResponses) UploadObjectWithBodyWithResponse(ctx context.Context, owner string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) { - rsp, err := c.UploadObjectWithBody(ctx, owner, repository, params, contentType, body, reqEditors...) - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r InviteMemberResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } - return ParseUploadObjectResponse(rsp) + return http.StatusText(0) } -// DeleteRepositoryWithResponse request returning *DeleteRepositoryResponse -func (c *ClientWithResponses) DeleteRepositoryWithResponse(ctx context.Context, owner string, repository string, params *DeleteRepositoryParams, reqEditors ...RequestEditorFn) (*DeleteRepositoryResponse, error) { - rsp, err := c.DeleteRepository(ctx, owner, repository, params, reqEditors...) - if err != nil { - return nil, err +// StatusCode returns HTTPResponse.StatusCode +func (r InviteMemberResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } - return ParseDeleteRepositoryResponse(rsp) + return 0 } -// GetRepositoryWithResponse request returning *GetRepositoryResponse -func (c *ClientWithResponses) GetRepositoryWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*GetRepositoryResponse, error) { - rsp, err := c.GetRepository(ctx, owner, repository, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetRepositoryResponse(rsp) +type ListMembersResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]Member } -// UpdateRepositoryWithBodyWithResponse request with arbitrary body returning *UpdateRepositoryResponse -func (c *ClientWithResponses) UpdateRepositoryWithBodyWithResponse(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) { - rsp, err := c.UpdateRepositoryWithBody(ctx, owner, repository, contentType, body, reqEditors...) - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r ListMembersResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } - return ParseUpdateRepositoryResponse(rsp) + return http.StatusText(0) } -func (c *ClientWithResponses) UpdateRepositoryWithResponse(ctx context.Context, owner string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) { - rsp, err := c.UpdateRepository(ctx, owner, repository, body, reqEditors...) - if err != nil { - return nil, err +// StatusCode returns HTTPResponse.StatusCode +func (r ListMembersResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } - return ParseUpdateRepositoryResponse(rsp) + return 0 } -// DeleteBranchWithResponse request returning *DeleteBranchResponse -func (c *ClientWithResponses) DeleteBranchWithResponse(ctx context.Context, owner string, repository string, params *DeleteBranchParams, reqEditors ...RequestEditorFn) (*DeleteBranchResponse, error) { - rsp, err := c.DeleteBranch(ctx, owner, repository, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseDeleteBranchResponse(rsp) +type ListMergeRequestsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *MergeRequestList } -// GetBranchWithResponse request returning *GetBranchResponse -func (c *ClientWithResponses) GetBranchWithResponse(ctx context.Context, owner string, repository string, params *GetBranchParams, reqEditors ...RequestEditorFn) (*GetBranchResponse, error) { - rsp, err := c.GetBranch(ctx, owner, repository, params, reqEditors...) - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r ListMergeRequestsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } - return ParseGetBranchResponse(rsp) + return http.StatusText(0) } -// CreateBranchWithBodyWithResponse request with arbitrary body returning *CreateBranchResponse -func (c *ClientWithResponses) CreateBranchWithBodyWithResponse(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) { - rsp, err := c.CreateBranchWithBody(ctx, owner, repository, contentType, body, reqEditors...) - if err != nil { - return nil, err +// StatusCode returns HTTPResponse.StatusCode +func (r ListMergeRequestsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } - return ParseCreateBranchResponse(rsp) + return 0 } -func (c *ClientWithResponses) CreateBranchWithResponse(ctx context.Context, owner string, repository string, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) { - rsp, err := c.CreateBranch(ctx, owner, repository, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseCreateBranchResponse(rsp) +type CreateMergeRequestResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *MergeRequest } -// ListBranchesWithResponse request returning *ListBranchesResponse -func (c *ClientWithResponses) ListBranchesWithResponse(ctx context.Context, owner string, repository string, params *ListBranchesParams, reqEditors ...RequestEditorFn) (*ListBranchesResponse, error) { - rsp, err := c.ListBranches(ctx, owner, repository, params, reqEditors...) - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r CreateMergeRequestResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } - return ParseListBranchesResponse(rsp) + return http.StatusText(0) } -// GetCommitChangesWithResponse request returning *GetCommitChangesResponse -func (c *ClientWithResponses) GetCommitChangesWithResponse(ctx context.Context, owner string, repository string, commitId string, params *GetCommitChangesParams, reqEditors ...RequestEditorFn) (*GetCommitChangesResponse, error) { - rsp, err := c.GetCommitChanges(ctx, owner, repository, commitId, params, reqEditors...) - if err != nil { - return nil, err +// StatusCode returns HTTPResponse.StatusCode +func (r CreateMergeRequestResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } - return ParseGetCommitChangesResponse(rsp) + return 0 } -// GetCommitsInRefWithResponse request returning *GetCommitsInRefResponse -func (c *ClientWithResponses) GetCommitsInRefWithResponse(ctx context.Context, owner string, repository string, params *GetCommitsInRefParams, reqEditors ...RequestEditorFn) (*GetCommitsInRefResponse, error) { - rsp, err := c.GetCommitsInRef(ctx, owner, repository, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetCommitsInRefResponse(rsp) +type GetMergeRequestResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *MergeRequestFullState } -// CompareCommitWithResponse request returning *CompareCommitResponse -func (c *ClientWithResponses) CompareCommitWithResponse(ctx context.Context, owner string, repository string, basehead string, params *CompareCommitParams, reqEditors ...RequestEditorFn) (*CompareCommitResponse, error) { - rsp, err := c.CompareCommit(ctx, owner, repository, basehead, params, reqEditors...) - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r GetMergeRequestResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } - return ParseCompareCommitResponse(rsp) + return http.StatusText(0) } -// GetEntriesInRefWithResponse request returning *GetEntriesInRefResponse -func (c *ClientWithResponses) GetEntriesInRefWithResponse(ctx context.Context, owner string, repository string, params *GetEntriesInRefParams, reqEditors ...RequestEditorFn) (*GetEntriesInRefResponse, error) { - rsp, err := c.GetEntriesInRef(ctx, owner, repository, params, reqEditors...) - if err != nil { - return nil, err +// StatusCode returns HTTPResponse.StatusCode +func (r GetMergeRequestResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } - return ParseGetEntriesInRefResponse(rsp) + return 0 } -// GetSetupStateWithResponse request returning *GetSetupStateResponse -func (c *ClientWithResponses) GetSetupStateWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetSetupStateResponse, error) { - rsp, err := c.GetSetupState(ctx, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetSetupStateResponse(rsp) +type UpdateMergeRequestResponse struct { + Body []byte + HTTPResponse *http.Response } -// DeleteAkskWithResponse request returning *DeleteAkskResponse -func (c *ClientWithResponses) DeleteAkskWithResponse(ctx context.Context, params *DeleteAkskParams, reqEditors ...RequestEditorFn) (*DeleteAkskResponse, error) { - rsp, err := c.DeleteAksk(ctx, params, reqEditors...) - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r UpdateMergeRequestResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } - return ParseDeleteAkskResponse(rsp) + return http.StatusText(0) } -// GetAkskWithResponse request returning *GetAkskResponse -func (c *ClientWithResponses) GetAkskWithResponse(ctx context.Context, params *GetAkskParams, reqEditors ...RequestEditorFn) (*GetAkskResponse, error) { - rsp, err := c.GetAksk(ctx, params, reqEditors...) - if err != nil { - return nil, err +// StatusCode returns HTTPResponse.StatusCode +func (r UpdateMergeRequestResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } - return ParseGetAkskResponse(rsp) + return 0 } -// CreateAkskWithResponse request returning *CreateAkskResponse -func (c *ClientWithResponses) CreateAkskWithResponse(ctx context.Context, params *CreateAkskParams, reqEditors ...RequestEditorFn) (*CreateAkskResponse, error) { - rsp, err := c.CreateAksk(ctx, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseCreateAkskResponse(rsp) +type MergeResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]Commit } -// ListAksksWithResponse request returning *ListAksksResponse -func (c *ClientWithResponses) ListAksksWithResponse(ctx context.Context, params *ListAksksParams, reqEditors ...RequestEditorFn) (*ListAksksResponse, error) { - rsp, err := c.ListAksks(ctx, params, reqEditors...) - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r MergeResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } - return ParseListAksksResponse(rsp) + return http.StatusText(0) } -// RefreshTokenWithResponse request returning *RefreshTokenResponse -func (c *ClientWithResponses) RefreshTokenWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*RefreshTokenResponse, error) { - rsp, err := c.RefreshToken(ctx, reqEditors...) - if err != nil { - return nil, err +// StatusCode returns HTTPResponse.StatusCode +func (r MergeResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } - return ParseRefreshTokenResponse(rsp) + return 0 } -// RegisterWithBodyWithResponse request with arbitrary body returning *RegisterResponse -func (c *ClientWithResponses) RegisterWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterResponse, error) { - rsp, err := c.RegisterWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseRegisterResponse(rsp) +type GetSetupStateResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *SetupState } -func (c *ClientWithResponses) RegisterWithResponse(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterResponse, error) { - rsp, err := c.Register(ctx, body, reqEditors...) - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r GetSetupStateResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } - return ParseRegisterResponse(rsp) + return http.StatusText(0) } -// ListRepositoryOfAuthenticatedUserWithResponse request returning *ListRepositoryOfAuthenticatedUserResponse -func (c *ClientWithResponses) ListRepositoryOfAuthenticatedUserWithResponse(ctx context.Context, params *ListRepositoryOfAuthenticatedUserParams, reqEditors ...RequestEditorFn) (*ListRepositoryOfAuthenticatedUserResponse, error) { - rsp, err := c.ListRepositoryOfAuthenticatedUser(ctx, params, reqEditors...) - if err != nil { - return nil, err +// StatusCode returns HTTPResponse.StatusCode +func (r GetSetupStateResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } - return ParseListRepositoryOfAuthenticatedUserResponse(rsp) + return 0 } -// CreateRepositoryWithBodyWithResponse request with arbitrary body returning *CreateRepositoryResponse -func (c *ClientWithResponses) CreateRepositoryWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateRepositoryResponse, error) { - rsp, err := c.CreateRepositoryWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseCreateRepositoryResponse(rsp) +type DeleteAkskResponse struct { + Body []byte + HTTPResponse *http.Response } -func (c *ClientWithResponses) CreateRepositoryWithResponse(ctx context.Context, body CreateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateRepositoryResponse, error) { - rsp, err := c.CreateRepository(ctx, body, reqEditors...) - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r DeleteAkskResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } - return ParseCreateRepositoryResponse(rsp) + return http.StatusText(0) } -// GetUserInfoWithResponse request returning *GetUserInfoResponse -func (c *ClientWithResponses) GetUserInfoWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetUserInfoResponse, error) { - rsp, err := c.GetUserInfo(ctx, reqEditors...) - if err != nil { - return nil, err +// StatusCode returns HTTPResponse.StatusCode +func (r DeleteAkskResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } - return ParseGetUserInfoResponse(rsp) + return 0 } -// ListRepositoryWithResponse request returning *ListRepositoryResponse -func (c *ClientWithResponses) ListRepositoryWithResponse(ctx context.Context, owner string, params *ListRepositoryParams, reqEditors ...RequestEditorFn) (*ListRepositoryResponse, error) { - rsp, err := c.ListRepository(ctx, owner, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseListRepositoryResponse(rsp) +type GetAkskResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Aksk } -// GetVersionWithResponse request returning *GetVersionResponse -func (c *ClientWithResponses) GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) { - rsp, err := c.GetVersion(ctx, reqEditors...) - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r GetAkskResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } - return ParseGetVersionResponse(rsp) + return http.StatusText(0) } -// DeleteWipWithResponse request returning *DeleteWipResponse -func (c *ClientWithResponses) DeleteWipWithResponse(ctx context.Context, owner string, repository string, params *DeleteWipParams, reqEditors ...RequestEditorFn) (*DeleteWipResponse, error) { - rsp, err := c.DeleteWip(ctx, owner, repository, params, reqEditors...) - if err != nil { - return nil, err +// StatusCode returns HTTPResponse.StatusCode +func (r GetAkskResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } - return ParseDeleteWipResponse(rsp) + return 0 } -// GetWipWithResponse request returning *GetWipResponse -func (c *ClientWithResponses) GetWipWithResponse(ctx context.Context, owner string, repository string, params *GetWipParams, reqEditors ...RequestEditorFn) (*GetWipResponse, error) { - rsp, err := c.GetWip(ctx, owner, repository, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetWipResponse(rsp) +type CreateAkskResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *Aksk } -// UpdateWipWithBodyWithResponse request with arbitrary body returning *UpdateWipResponse -func (c *ClientWithResponses) UpdateWipWithBodyWithResponse(ctx context.Context, owner string, repository string, params *UpdateWipParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateWipResponse, error) { - rsp, err := c.UpdateWipWithBody(ctx, owner, repository, params, contentType, body, reqEditors...) - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r CreateAkskResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } - return ParseUpdateWipResponse(rsp) + return http.StatusText(0) } -func (c *ClientWithResponses) UpdateWipWithResponse(ctx context.Context, owner string, repository string, params *UpdateWipParams, body UpdateWipJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateWipResponse, error) { - rsp, err := c.UpdateWip(ctx, owner, repository, params, body, reqEditors...) - if err != nil { - return nil, err +// StatusCode returns HTTPResponse.StatusCode +func (r CreateAkskResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } - return ParseUpdateWipResponse(rsp) + return 0 } -// GetWipChangesWithResponse request returning *GetWipChangesResponse -func (c *ClientWithResponses) GetWipChangesWithResponse(ctx context.Context, owner string, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*GetWipChangesResponse, error) { - rsp, err := c.GetWipChanges(ctx, owner, repository, params, reqEditors...) - if err != nil { - return nil, err +type ListAksksResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *AkskList +} + +// Status returns HTTPResponse.Status +func (r ListAksksResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } - return ParseGetWipChangesResponse(rsp) + return http.StatusText(0) } -// CommitWipWithResponse request returning *CommitWipResponse -func (c *ClientWithResponses) CommitWipWithResponse(ctx context.Context, owner string, repository string, params *CommitWipParams, reqEditors ...RequestEditorFn) (*CommitWipResponse, error) { - rsp, err := c.CommitWip(ctx, owner, repository, params, reqEditors...) - if err != nil { - return nil, err +// StatusCode returns HTTPResponse.StatusCode +func (r ListAksksResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } - return ParseCommitWipResponse(rsp) + return 0 } -// ListWipWithResponse request returning *ListWipResponse -func (c *ClientWithResponses) ListWipWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*ListWipResponse, error) { - rsp, err := c.ListWip(ctx, owner, repository, reqEditors...) - if err != nil { - return nil, err - } - return ParseListWipResponse(rsp) +type RefreshTokenResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *AuthenticationToken } -// RevertWipChangesWithResponse request returning *RevertWipChangesResponse -func (c *ClientWithResponses) RevertWipChangesWithResponse(ctx context.Context, owner string, repository string, params *RevertWipChangesParams, reqEditors ...RequestEditorFn) (*RevertWipChangesResponse, error) { - rsp, err := c.RevertWipChanges(ctx, owner, repository, params, reqEditors...) - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r RefreshTokenResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } - return ParseRevertWipChangesResponse(rsp) + return http.StatusText(0) } -// ParseLoginResponse parses an HTTP response from a LoginWithResponse call -func ParseLoginResponse(rsp *http.Response) (*LoginResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err +// StatusCode returns HTTPResponse.StatusCode +func (r RefreshTokenResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } + return 0 +} - response := &LoginResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } +type RegisterResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *UserInfo +} - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest AuthenticationToken - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest +// Status returns HTTPResponse.Status +func (r RegisterResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} +// StatusCode returns HTTPResponse.StatusCode +func (r RegisterResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } + return 0 +} - return response, nil +type ListRepositoryOfAuthenticatedUserResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *RepositoryList } -// ParseLogoutResponse parses an HTTP response from a LogoutWithResponse call -func ParseLogoutResponse(rsp *http.Response) (*LogoutResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r ListRepositoryOfAuthenticatedUserResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } + return http.StatusText(0) +} - response := &LogoutResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// StatusCode returns HTTPResponse.StatusCode +func (r ListRepositoryOfAuthenticatedUserResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } - - return response, nil + return 0 } -// ParseGetMergeRequestResponse parses an HTTP response from a GetMergeRequestWithResponse call -func ParseGetMergeRequestResponse(rsp *http.Response) (*GetMergeRequestResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } +type CreateRepositoryResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *Repository +} - response := &GetMergeRequestResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// Status returns HTTPResponse.Status +func (r CreateRepositoryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } + return http.StatusText(0) +} - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest MergeRequestFullState - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - +// StatusCode returns HTTPResponse.StatusCode +func (r CreateRepositoryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } + return 0 +} - return response, nil +type GetUserInfoResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *UserInfo } -// ParseUpdateMergeRequestResponse parses an HTTP response from a UpdateMergeRequestWithResponse call -func ParseUpdateMergeRequestResponse(rsp *http.Response) (*UpdateMergeRequestResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r GetUserInfoResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } + return http.StatusText(0) +} - response := &UpdateMergeRequestResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// StatusCode returns HTTPResponse.StatusCode +func (r GetUserInfoResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } + return 0 +} - return response, nil +type ListRepositoryResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *RepositoryList } -// ParseListMergeRequestsResponse parses an HTTP response from a ListMergeRequestsWithResponse call -func ParseListMergeRequestsResponse(rsp *http.Response) (*ListMergeRequestsResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r ListRepositoryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } + return http.StatusText(0) +} - response := &ListMergeRequestsResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// StatusCode returns HTTPResponse.StatusCode +func (r ListRepositoryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } + return 0 +} - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest MergeRequestList - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest +type GetVersionResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *VersionResult +} + +// Status returns HTTPResponse.Status +func (r GetVersionResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} +// StatusCode returns HTTPResponse.StatusCode +func (r GetVersionResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } + return 0 +} - return response, nil +type DeleteWipResponse struct { + Body []byte + HTTPResponse *http.Response } -// ParseCreateMergeRequestResponse parses an HTTP response from a CreateMergeRequestWithResponse call -func ParseCreateMergeRequestResponse(rsp *http.Response) (*CreateMergeRequestResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r DeleteWipResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } + return http.StatusText(0) +} - response := &CreateMergeRequestResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// StatusCode returns HTTPResponse.StatusCode +func (r DeleteWipResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } + return 0 +} - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest MergeRequest - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON201 = &dest +type GetWipResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Wip +} +// Status returns HTTPResponse.Status +func (r GetWipResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } - - return response, nil + return http.StatusText(0) } -// ParseMergeResponse parses an HTTP response from a MergeWithResponse call -func ParseMergeResponse(rsp *http.Response) (*MergeResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err +// StatusCode returns HTTPResponse.StatusCode +func (r GetWipResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } + return 0 +} - response := &MergeResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } +type UpdateWipResponse struct { + Body []byte + HTTPResponse *http.Response +} - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []Commit - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest +// Status returns HTTPResponse.Status +func (r UpdateWipResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} +// StatusCode returns HTTPResponse.StatusCode +func (r UpdateWipResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } + return 0 +} - return response, nil +type GetWipChangesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]Change } -// ParseDeleteObjectResponse parses an HTTP response from a DeleteObjectWithResponse call -func ParseDeleteObjectResponse(rsp *http.Response) (*DeleteObjectResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r GetWipChangesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } + return http.StatusText(0) +} - response := &DeleteObjectResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// StatusCode returns HTTPResponse.StatusCode +func (r GetWipChangesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } + return 0 +} - return response, nil +type CommitWipResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *Wip } -// ParseGetObjectResponse parses an HTTP response from a GetObjectWithResponse call -func ParseGetObjectResponse(rsp *http.Response) (*GetObjectResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r CommitWipResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } + return http.StatusText(0) +} - response := &GetObjectResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// StatusCode returns HTTPResponse.StatusCode +func (r CommitWipResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } + return 0 +} - return response, nil +type ListWipResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]Wip } -// ParseHeadObjectResponse parses an HTTP response from a HeadObjectWithResponse call -func ParseHeadObjectResponse(rsp *http.Response) (*HeadObjectResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r ListWipResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } + return http.StatusText(0) +} - response := &HeadObjectResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// StatusCode returns HTTPResponse.StatusCode +func (r ListWipResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } + return 0 +} - return response, nil +type RevertWipChangesResponse struct { + Body []byte + HTTPResponse *http.Response } -// ParseUploadObjectResponse parses an HTTP response from a UploadObjectWithResponse call -func ParseUploadObjectResponse(rsp *http.Response) (*UploadObjectResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r RevertWipChangesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } + return http.StatusText(0) +} - response := &UploadObjectResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// StatusCode returns HTTPResponse.StatusCode +func (r RevertWipChangesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } + return 0 +} - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest ObjectStats - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON201 = &dest - +// LoginWithBodyWithResponse request with arbitrary body returning *LoginResponse +func (c *ClientWithResponses) LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) { + rsp, err := c.LoginWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err } - - return response, nil + return ParseLoginResponse(rsp) } -// ParseDeleteRepositoryResponse parses an HTTP response from a DeleteRepositoryWithResponse call -func ParseDeleteRepositoryResponse(rsp *http.Response) (*DeleteRepositoryResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() +func (c *ClientWithResponses) LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) { + rsp, err := c.Login(ctx, body, reqEditors...) if err != nil { return nil, err } + return ParseLoginResponse(rsp) +} - response := &DeleteRepositoryResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// LogoutWithResponse request returning *LogoutResponse +func (c *ClientWithResponses) LogoutWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*LogoutResponse, error) { + rsp, err := c.Logout(ctx, reqEditors...) + if err != nil { + return nil, err } - - return response, nil + return ParseLogoutResponse(rsp) } -// ParseGetRepositoryResponse parses an HTTP response from a GetRepositoryWithResponse call -func ParseGetRepositoryResponse(rsp *http.Response) (*GetRepositoryResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() +// ListRepoGroupWithResponse request returning *ListRepoGroupResponse +func (c *ClientWithResponses) ListRepoGroupWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListRepoGroupResponse, error) { + rsp, err := c.ListRepoGroup(ctx, reqEditors...) if err != nil { return nil, err } + return ParseListRepoGroupResponse(rsp) +} - response := &GetRepositoryResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// DeleteObjectWithResponse request returning *DeleteObjectResponse +func (c *ClientWithResponses) DeleteObjectWithResponse(ctx context.Context, owner string, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) { + rsp, err := c.DeleteObject(ctx, owner, repository, params, reqEditors...) + if err != nil { + return nil, err } + return ParseDeleteObjectResponse(rsp) +} - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest Repository - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - +// GetObjectWithResponse request returning *GetObjectResponse +func (c *ClientWithResponses) GetObjectWithResponse(ctx context.Context, owner string, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*GetObjectResponse, error) { + rsp, err := c.GetObject(ctx, owner, repository, params, reqEditors...) + if err != nil { + return nil, err } - - return response, nil + return ParseGetObjectResponse(rsp) } -// ParseUpdateRepositoryResponse parses an HTTP response from a UpdateRepositoryWithResponse call -func ParseUpdateRepositoryResponse(rsp *http.Response) (*UpdateRepositoryResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() +// HeadObjectWithResponse request returning *HeadObjectResponse +func (c *ClientWithResponses) HeadObjectWithResponse(ctx context.Context, owner string, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*HeadObjectResponse, error) { + rsp, err := c.HeadObject(ctx, owner, repository, params, reqEditors...) if err != nil { return nil, err } + return ParseHeadObjectResponse(rsp) +} - response := &UpdateRepositoryResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// UploadObjectWithBodyWithResponse request with arbitrary body returning *UploadObjectResponse +func (c *ClientWithResponses) UploadObjectWithBodyWithResponse(ctx context.Context, owner string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) { + rsp, err := c.UploadObjectWithBody(ctx, owner, repository, params, contentType, body, reqEditors...) + if err != nil { + return nil, err } - - return response, nil + return ParseUploadObjectResponse(rsp) } -// ParseDeleteBranchResponse parses an HTTP response from a DeleteBranchWithResponse call -func ParseDeleteBranchResponse(rsp *http.Response) (*DeleteBranchResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() +// DeleteRepositoryWithResponse request returning *DeleteRepositoryResponse +func (c *ClientWithResponses) DeleteRepositoryWithResponse(ctx context.Context, owner string, repository string, params *DeleteRepositoryParams, reqEditors ...RequestEditorFn) (*DeleteRepositoryResponse, error) { + rsp, err := c.DeleteRepository(ctx, owner, repository, params, reqEditors...) if err != nil { return nil, err } + return ParseDeleteRepositoryResponse(rsp) +} - response := &DeleteBranchResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// GetRepositoryWithResponse request returning *GetRepositoryResponse +func (c *ClientWithResponses) GetRepositoryWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*GetRepositoryResponse, error) { + rsp, err := c.GetRepository(ctx, owner, repository, reqEditors...) + if err != nil { + return nil, err } - - return response, nil + return ParseGetRepositoryResponse(rsp) } -// ParseGetBranchResponse parses an HTTP response from a GetBranchWithResponse call -func ParseGetBranchResponse(rsp *http.Response) (*GetBranchResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() +// UpdateRepositoryWithBodyWithResponse request with arbitrary body returning *UpdateRepositoryResponse +func (c *ClientWithResponses) UpdateRepositoryWithBodyWithResponse(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) { + rsp, err := c.UpdateRepositoryWithBody(ctx, owner, repository, contentType, body, reqEditors...) if err != nil { return nil, err } + return ParseUpdateRepositoryResponse(rsp) +} - response := &GetBranchResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +func (c *ClientWithResponses) UpdateRepositoryWithResponse(ctx context.Context, owner string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) { + rsp, err := c.UpdateRepository(ctx, owner, repository, body, reqEditors...) + if err != nil { + return nil, err } + return ParseUpdateRepositoryResponse(rsp) +} - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest Branch - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - +// DeleteBranchWithResponse request returning *DeleteBranchResponse +func (c *ClientWithResponses) DeleteBranchWithResponse(ctx context.Context, owner string, repository string, params *DeleteBranchParams, reqEditors ...RequestEditorFn) (*DeleteBranchResponse, error) { + rsp, err := c.DeleteBranch(ctx, owner, repository, params, reqEditors...) + if err != nil { + return nil, err } - - return response, nil + return ParseDeleteBranchResponse(rsp) } -// ParseCreateBranchResponse parses an HTTP response from a CreateBranchWithResponse call -func ParseCreateBranchResponse(rsp *http.Response) (*CreateBranchResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() +// GetBranchWithResponse request returning *GetBranchResponse +func (c *ClientWithResponses) GetBranchWithResponse(ctx context.Context, owner string, repository string, params *GetBranchParams, reqEditors ...RequestEditorFn) (*GetBranchResponse, error) { + rsp, err := c.GetBranch(ctx, owner, repository, params, reqEditors...) if err != nil { return nil, err } - - response := &CreateBranchResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest BranchCreation - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON201 = &dest - - } - - return response, nil + return ParseGetBranchResponse(rsp) } -// ParseListBranchesResponse parses an HTTP response from a ListBranchesWithResponse call -func ParseListBranchesResponse(rsp *http.Response) (*ListBranchesResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() +// CreateBranchWithBodyWithResponse request with arbitrary body returning *CreateBranchResponse +func (c *ClientWithResponses) CreateBranchWithBodyWithResponse(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) { + rsp, err := c.CreateBranchWithBody(ctx, owner, repository, contentType, body, reqEditors...) if err != nil { return nil, err } + return ParseCreateBranchResponse(rsp) +} - response := &ListBranchesResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest BranchList - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - +func (c *ClientWithResponses) CreateBranchWithResponse(ctx context.Context, owner string, repository string, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) { + rsp, err := c.CreateBranch(ctx, owner, repository, body, reqEditors...) + if err != nil { + return nil, err } - - return response, nil + return ParseCreateBranchResponse(rsp) } -// ParseGetCommitChangesResponse parses an HTTP response from a GetCommitChangesWithResponse call -func ParseGetCommitChangesResponse(rsp *http.Response) (*GetCommitChangesResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() +// ListBranchesWithResponse request returning *ListBranchesResponse +func (c *ClientWithResponses) ListBranchesWithResponse(ctx context.Context, owner string, repository string, params *ListBranchesParams, reqEditors ...RequestEditorFn) (*ListBranchesResponse, error) { + rsp, err := c.ListBranches(ctx, owner, repository, params, reqEditors...) if err != nil { return nil, err } + return ParseListBranchesResponse(rsp) +} - response := &GetCommitChangesResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// GetCommitChangesWithResponse request returning *GetCommitChangesResponse +func (c *ClientWithResponses) GetCommitChangesWithResponse(ctx context.Context, owner string, repository string, commitId string, params *GetCommitChangesParams, reqEditors ...RequestEditorFn) (*GetCommitChangesResponse, error) { + rsp, err := c.GetCommitChanges(ctx, owner, repository, commitId, params, reqEditors...) + if err != nil { + return nil, err } + return ParseGetCommitChangesResponse(rsp) +} - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []Change - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - +// GetCommitsInRefWithResponse request returning *GetCommitsInRefResponse +func (c *ClientWithResponses) GetCommitsInRefWithResponse(ctx context.Context, owner string, repository string, params *GetCommitsInRefParams, reqEditors ...RequestEditorFn) (*GetCommitsInRefResponse, error) { + rsp, err := c.GetCommitsInRef(ctx, owner, repository, params, reqEditors...) + if err != nil { + return nil, err } - - return response, nil + return ParseGetCommitsInRefResponse(rsp) } -// ParseGetCommitsInRefResponse parses an HTTP response from a GetCommitsInRefWithResponse call -func ParseGetCommitsInRefResponse(rsp *http.Response) (*GetCommitsInRefResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() +// CompareCommitWithResponse request returning *CompareCommitResponse +func (c *ClientWithResponses) CompareCommitWithResponse(ctx context.Context, owner string, repository string, basehead string, params *CompareCommitParams, reqEditors ...RequestEditorFn) (*CompareCommitResponse, error) { + rsp, err := c.CompareCommit(ctx, owner, repository, basehead, params, reqEditors...) if err != nil { return nil, err } + return ParseCompareCommitResponse(rsp) +} - response := &GetCommitsInRefResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// GetEntriesInRefWithResponse request returning *GetEntriesInRefResponse +func (c *ClientWithResponses) GetEntriesInRefWithResponse(ctx context.Context, owner string, repository string, params *GetEntriesInRefParams, reqEditors ...RequestEditorFn) (*GetEntriesInRefResponse, error) { + rsp, err := c.GetEntriesInRef(ctx, owner, repository, params, reqEditors...) + if err != nil { + return nil, err } + return ParseGetEntriesInRefResponse(rsp) +} - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []Commit - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - +// RevokeMemberWithResponse request returning *RevokeMemberResponse +func (c *ClientWithResponses) RevokeMemberWithResponse(ctx context.Context, owner string, repository string, params *RevokeMemberParams, reqEditors ...RequestEditorFn) (*RevokeMemberResponse, error) { + rsp, err := c.RevokeMember(ctx, owner, repository, params, reqEditors...) + if err != nil { + return nil, err } - - return response, nil + return ParseRevokeMemberResponse(rsp) } -// ParseCompareCommitResponse parses an HTTP response from a CompareCommitWithResponse call -func ParseCompareCommitResponse(rsp *http.Response) (*CompareCommitResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() +// UpdateMemberGroupWithResponse request returning *UpdateMemberGroupResponse +func (c *ClientWithResponses) UpdateMemberGroupWithResponse(ctx context.Context, owner string, repository string, params *UpdateMemberGroupParams, reqEditors ...RequestEditorFn) (*UpdateMemberGroupResponse, error) { + rsp, err := c.UpdateMemberGroup(ctx, owner, repository, params, reqEditors...) if err != nil { return nil, err } + return ParseUpdateMemberGroupResponse(rsp) +} - response := &CompareCommitResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// InviteMemberWithResponse request returning *InviteMemberResponse +func (c *ClientWithResponses) InviteMemberWithResponse(ctx context.Context, owner string, repository string, params *InviteMemberParams, reqEditors ...RequestEditorFn) (*InviteMemberResponse, error) { + rsp, err := c.InviteMember(ctx, owner, repository, params, reqEditors...) + if err != nil { + return nil, err } + return ParseInviteMemberResponse(rsp) +} - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []Change - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - +// ListMembersWithResponse request returning *ListMembersResponse +func (c *ClientWithResponses) ListMembersWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*ListMembersResponse, error) { + rsp, err := c.ListMembers(ctx, owner, repository, reqEditors...) + if err != nil { + return nil, err } - - return response, nil + return ParseListMembersResponse(rsp) } -// ParseGetEntriesInRefResponse parses an HTTP response from a GetEntriesInRefWithResponse call -func ParseGetEntriesInRefResponse(rsp *http.Response) (*GetEntriesInRefResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() +// ListMergeRequestsWithResponse request returning *ListMergeRequestsResponse +func (c *ClientWithResponses) ListMergeRequestsWithResponse(ctx context.Context, owner string, repository string, params *ListMergeRequestsParams, reqEditors ...RequestEditorFn) (*ListMergeRequestsResponse, error) { + rsp, err := c.ListMergeRequests(ctx, owner, repository, params, reqEditors...) if err != nil { return nil, err } + return ParseListMergeRequestsResponse(rsp) +} - response := &GetEntriesInRefResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// CreateMergeRequestWithBodyWithResponse request with arbitrary body returning *CreateMergeRequestResponse +func (c *ClientWithResponses) CreateMergeRequestWithBodyWithResponse(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateMergeRequestResponse, error) { + rsp, err := c.CreateMergeRequestWithBody(ctx, owner, repository, contentType, body, reqEditors...) + if err != nil { + return nil, err } + return ParseCreateMergeRequestResponse(rsp) +} - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []FullTreeEntry - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - +func (c *ClientWithResponses) CreateMergeRequestWithResponse(ctx context.Context, owner string, repository string, body CreateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateMergeRequestResponse, error) { + rsp, err := c.CreateMergeRequest(ctx, owner, repository, body, reqEditors...) + if err != nil { + return nil, err } - - return response, nil + return ParseCreateMergeRequestResponse(rsp) } -// ParseGetSetupStateResponse parses an HTTP response from a GetSetupStateWithResponse call -func ParseGetSetupStateResponse(rsp *http.Response) (*GetSetupStateResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() +// GetMergeRequestWithResponse request returning *GetMergeRequestResponse +func (c *ClientWithResponses) GetMergeRequestWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, reqEditors ...RequestEditorFn) (*GetMergeRequestResponse, error) { + rsp, err := c.GetMergeRequest(ctx, owner, repository, mrSeq, reqEditors...) if err != nil { return nil, err } + return ParseGetMergeRequestResponse(rsp) +} - response := &GetSetupStateResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// UpdateMergeRequestWithBodyWithResponse request with arbitrary body returning *UpdateMergeRequestResponse +func (c *ClientWithResponses) UpdateMergeRequestWithBodyWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateMergeRequestResponse, error) { + rsp, err := c.UpdateMergeRequestWithBody(ctx, owner, repository, mrSeq, contentType, body, reqEditors...) + if err != nil { + return nil, err } + return ParseUpdateMergeRequestResponse(rsp) +} - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest SetupState - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - +func (c *ClientWithResponses) UpdateMergeRequestWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, body UpdateMergeRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateMergeRequestResponse, error) { + rsp, err := c.UpdateMergeRequest(ctx, owner, repository, mrSeq, body, reqEditors...) + if err != nil { + return nil, err } - - return response, nil + return ParseUpdateMergeRequestResponse(rsp) } -// ParseDeleteAkskResponse parses an HTTP response from a DeleteAkskWithResponse call -func ParseDeleteAkskResponse(rsp *http.Response) (*DeleteAkskResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() +// MergeWithBodyWithResponse request with arbitrary body returning *MergeResponse +func (c *ClientWithResponses) MergeWithBodyWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MergeResponse, error) { + rsp, err := c.MergeWithBody(ctx, owner, repository, mrSeq, contentType, body, reqEditors...) if err != nil { return nil, err } + return ParseMergeResponse(rsp) +} - response := &DeleteAkskResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +func (c *ClientWithResponses) MergeWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, body MergeJSONRequestBody, reqEditors ...RequestEditorFn) (*MergeResponse, error) { + rsp, err := c.Merge(ctx, owner, repository, mrSeq, body, reqEditors...) + if err != nil { + return nil, err } - - return response, nil + return ParseMergeResponse(rsp) } -// ParseGetAkskResponse parses an HTTP response from a GetAkskWithResponse call -func ParseGetAkskResponse(rsp *http.Response) (*GetAkskResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() +// GetSetupStateWithResponse request returning *GetSetupStateResponse +func (c *ClientWithResponses) GetSetupStateWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetSetupStateResponse, error) { + rsp, err := c.GetSetupState(ctx, reqEditors...) if err != nil { return nil, err } + return ParseGetSetupStateResponse(rsp) +} - response := &GetAkskResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// DeleteAkskWithResponse request returning *DeleteAkskResponse +func (c *ClientWithResponses) DeleteAkskWithResponse(ctx context.Context, params *DeleteAkskParams, reqEditors ...RequestEditorFn) (*DeleteAkskResponse, error) { + rsp, err := c.DeleteAksk(ctx, params, reqEditors...) + if err != nil { + return nil, err } + return ParseDeleteAkskResponse(rsp) +} - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest Aksk - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - +// GetAkskWithResponse request returning *GetAkskResponse +func (c *ClientWithResponses) GetAkskWithResponse(ctx context.Context, params *GetAkskParams, reqEditors ...RequestEditorFn) (*GetAkskResponse, error) { + rsp, err := c.GetAksk(ctx, params, reqEditors...) + if err != nil { + return nil, err } + return ParseGetAkskResponse(rsp) +} - return response, nil +// CreateAkskWithResponse request returning *CreateAkskResponse +func (c *ClientWithResponses) CreateAkskWithResponse(ctx context.Context, params *CreateAkskParams, reqEditors ...RequestEditorFn) (*CreateAkskResponse, error) { + rsp, err := c.CreateAksk(ctx, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateAkskResponse(rsp) } -// ParseCreateAkskResponse parses an HTTP response from a CreateAkskWithResponse call -func ParseCreateAkskResponse(rsp *http.Response) (*CreateAkskResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() +// ListAksksWithResponse request returning *ListAksksResponse +func (c *ClientWithResponses) ListAksksWithResponse(ctx context.Context, params *ListAksksParams, reqEditors ...RequestEditorFn) (*ListAksksResponse, error) { + rsp, err := c.ListAksks(ctx, params, reqEditors...) if err != nil { return nil, err } + return ParseListAksksResponse(rsp) +} - response := &CreateAkskResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// RefreshTokenWithResponse request returning *RefreshTokenResponse +func (c *ClientWithResponses) RefreshTokenWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*RefreshTokenResponse, error) { + rsp, err := c.RefreshToken(ctx, reqEditors...) + if err != nil { + return nil, err } + return ParseRefreshTokenResponse(rsp) +} - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest Aksk - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON201 = &dest +// RegisterWithBodyWithResponse request with arbitrary body returning *RegisterResponse +func (c *ClientWithResponses) RegisterWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterResponse, error) { + rsp, err := c.RegisterWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseRegisterResponse(rsp) +} +func (c *ClientWithResponses) RegisterWithResponse(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterResponse, error) { + rsp, err := c.Register(ctx, body, reqEditors...) + if err != nil { + return nil, err } + return ParseRegisterResponse(rsp) +} - return response, nil +// ListRepositoryOfAuthenticatedUserWithResponse request returning *ListRepositoryOfAuthenticatedUserResponse +func (c *ClientWithResponses) ListRepositoryOfAuthenticatedUserWithResponse(ctx context.Context, params *ListRepositoryOfAuthenticatedUserParams, reqEditors ...RequestEditorFn) (*ListRepositoryOfAuthenticatedUserResponse, error) { + rsp, err := c.ListRepositoryOfAuthenticatedUser(ctx, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseListRepositoryOfAuthenticatedUserResponse(rsp) } -// ParseListAksksResponse parses an HTTP response from a ListAksksWithResponse call -func ParseListAksksResponse(rsp *http.Response) (*ListAksksResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() +// CreateRepositoryWithBodyWithResponse request with arbitrary body returning *CreateRepositoryResponse +func (c *ClientWithResponses) CreateRepositoryWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateRepositoryResponse, error) { + rsp, err := c.CreateRepositoryWithBody(ctx, contentType, body, reqEditors...) if err != nil { return nil, err } + return ParseCreateRepositoryResponse(rsp) +} - response := &ListAksksResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +func (c *ClientWithResponses) CreateRepositoryWithResponse(ctx context.Context, body CreateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateRepositoryResponse, error) { + rsp, err := c.CreateRepository(ctx, body, reqEditors...) + if err != nil { + return nil, err } + return ParseCreateRepositoryResponse(rsp) +} - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest AkskList - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest +// GetUserInfoWithResponse request returning *GetUserInfoResponse +func (c *ClientWithResponses) GetUserInfoWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetUserInfoResponse, error) { + rsp, err := c.GetUserInfo(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetUserInfoResponse(rsp) +} +// ListRepositoryWithResponse request returning *ListRepositoryResponse +func (c *ClientWithResponses) ListRepositoryWithResponse(ctx context.Context, owner string, params *ListRepositoryParams, reqEditors ...RequestEditorFn) (*ListRepositoryResponse, error) { + rsp, err := c.ListRepository(ctx, owner, params, reqEditors...) + if err != nil { + return nil, err } + return ParseListRepositoryResponse(rsp) +} - return response, nil +// GetVersionWithResponse request returning *GetVersionResponse +func (c *ClientWithResponses) GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) { + rsp, err := c.GetVersion(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetVersionResponse(rsp) } -// ParseRefreshTokenResponse parses an HTTP response from a RefreshTokenWithResponse call -func ParseRefreshTokenResponse(rsp *http.Response) (*RefreshTokenResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() +// DeleteWipWithResponse request returning *DeleteWipResponse +func (c *ClientWithResponses) DeleteWipWithResponse(ctx context.Context, owner string, repository string, params *DeleteWipParams, reqEditors ...RequestEditorFn) (*DeleteWipResponse, error) { + rsp, err := c.DeleteWip(ctx, owner, repository, params, reqEditors...) if err != nil { return nil, err } + return ParseDeleteWipResponse(rsp) +} - response := &RefreshTokenResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// GetWipWithResponse request returning *GetWipResponse +func (c *ClientWithResponses) GetWipWithResponse(ctx context.Context, owner string, repository string, params *GetWipParams, reqEditors ...RequestEditorFn) (*GetWipResponse, error) { + rsp, err := c.GetWip(ctx, owner, repository, params, reqEditors...) + if err != nil { + return nil, err } + return ParseGetWipResponse(rsp) +} - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest AuthenticationToken - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest +// UpdateWipWithBodyWithResponse request with arbitrary body returning *UpdateWipResponse +func (c *ClientWithResponses) UpdateWipWithBodyWithResponse(ctx context.Context, owner string, repository string, params *UpdateWipParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateWipResponse, error) { + rsp, err := c.UpdateWipWithBody(ctx, owner, repository, params, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateWipResponse(rsp) +} +func (c *ClientWithResponses) UpdateWipWithResponse(ctx context.Context, owner string, repository string, params *UpdateWipParams, body UpdateWipJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateWipResponse, error) { + rsp, err := c.UpdateWip(ctx, owner, repository, params, body, reqEditors...) + if err != nil { + return nil, err } + return ParseUpdateWipResponse(rsp) +} - return response, nil +// GetWipChangesWithResponse request returning *GetWipChangesResponse +func (c *ClientWithResponses) GetWipChangesWithResponse(ctx context.Context, owner string, repository string, params *GetWipChangesParams, reqEditors ...RequestEditorFn) (*GetWipChangesResponse, error) { + rsp, err := c.GetWipChanges(ctx, owner, repository, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetWipChangesResponse(rsp) } -// ParseRegisterResponse parses an HTTP response from a RegisterWithResponse call -func ParseRegisterResponse(rsp *http.Response) (*RegisterResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() +// CommitWipWithResponse request returning *CommitWipResponse +func (c *ClientWithResponses) CommitWipWithResponse(ctx context.Context, owner string, repository string, params *CommitWipParams, reqEditors ...RequestEditorFn) (*CommitWipResponse, error) { + rsp, err := c.CommitWip(ctx, owner, repository, params, reqEditors...) if err != nil { return nil, err } + return ParseCommitWipResponse(rsp) +} - response := &RegisterResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// ListWipWithResponse request returning *ListWipResponse +func (c *ClientWithResponses) ListWipWithResponse(ctx context.Context, owner string, repository string, reqEditors ...RequestEditorFn) (*ListWipResponse, error) { + rsp, err := c.ListWip(ctx, owner, repository, reqEditors...) + if err != nil { + return nil, err } + return ParseListWipResponse(rsp) +} - return response, nil +// RevertWipChangesWithResponse request returning *RevertWipChangesResponse +func (c *ClientWithResponses) RevertWipChangesWithResponse(ctx context.Context, owner string, repository string, params *RevertWipChangesParams, reqEditors ...RequestEditorFn) (*RevertWipChangesResponse, error) { + rsp, err := c.RevertWipChanges(ctx, owner, repository, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseRevertWipChangesResponse(rsp) } -// ParseListRepositoryOfAuthenticatedUserResponse parses an HTTP response from a ListRepositoryOfAuthenticatedUserWithResponse call -func ParseListRepositoryOfAuthenticatedUserResponse(rsp *http.Response) (*ListRepositoryOfAuthenticatedUserResponse, error) { +// ParseLoginResponse parses an HTTP response from a LoginWithResponse call +func ParseLoginResponse(rsp *http.Response) (*LoginResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &ListRepositoryOfAuthenticatedUserResponse{ + response := &LoginResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest RepositoryList + var dest AuthenticationToken if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -6191,48 +6085,38 @@ func ParseListRepositoryOfAuthenticatedUserResponse(rsp *http.Response) (*ListRe return response, nil } -// ParseCreateRepositoryResponse parses an HTTP response from a CreateRepositoryWithResponse call -func ParseCreateRepositoryResponse(rsp *http.Response) (*CreateRepositoryResponse, error) { +// ParseLogoutResponse parses an HTTP response from a LogoutWithResponse call +func ParseLogoutResponse(rsp *http.Response) (*LogoutResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CreateRepositoryResponse{ + response := &LogoutResponse{ Body: bodyBytes, HTTPResponse: rsp, } - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest Repository - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON201 = &dest - - } - return response, nil } -// ParseGetUserInfoResponse parses an HTTP response from a GetUserInfoWithResponse call -func ParseGetUserInfoResponse(rsp *http.Response) (*GetUserInfoResponse, error) { +// ParseListRepoGroupResponse parses an HTTP response from a ListRepoGroupWithResponse call +func ParseListRepoGroupResponse(rsp *http.Response) (*ListRepoGroupResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetUserInfoResponse{ + response := &ListRepoGroupResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest UserInfo + var dest []Group if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -6243,67 +6127,89 @@ func ParseGetUserInfoResponse(rsp *http.Response) (*GetUserInfoResponse, error) return response, nil } -// ParseListRepositoryResponse parses an HTTP response from a ListRepositoryWithResponse call -func ParseListRepositoryResponse(rsp *http.Response) (*ListRepositoryResponse, error) { +// ParseDeleteObjectResponse parses an HTTP response from a DeleteObjectWithResponse call +func ParseDeleteObjectResponse(rsp *http.Response) (*DeleteObjectResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &ListRepositoryResponse{ + response := &DeleteObjectResponse{ Body: bodyBytes, HTTPResponse: rsp, } - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest RepositoryList - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - } - return response, nil } -// ParseGetVersionResponse parses an HTTP response from a GetVersionWithResponse call -func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) { +// ParseGetObjectResponse parses an HTTP response from a GetObjectWithResponse call +func ParseGetObjectResponse(rsp *http.Response) (*GetObjectResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetVersionResponse{ + response := &GetObjectResponse{ Body: bodyBytes, HTTPResponse: rsp, } - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest VersionResult - if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return response, nil +} + +// ParseHeadObjectResponse parses an HTTP response from a HeadObjectWithResponse call +func ParseHeadObjectResponse(rsp *http.Response) (*HeadObjectResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &HeadObjectResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseUploadObjectResponse parses an HTTP response from a UploadObjectWithResponse call +func ParseUploadObjectResponse(rsp *http.Response) (*UploadObjectResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UploadObjectResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest ObjectStats + if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON200 = &dest + response.JSON201 = &dest } return response, nil } -// ParseDeleteWipResponse parses an HTTP response from a DeleteWipWithResponse call -func ParseDeleteWipResponse(rsp *http.Response) (*DeleteWipResponse, error) { +// ParseDeleteRepositoryResponse parses an HTTP response from a DeleteRepositoryWithResponse call +func ParseDeleteRepositoryResponse(rsp *http.Response) (*DeleteRepositoryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &DeleteWipResponse{ + response := &DeleteRepositoryResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -6311,22 +6217,22 @@ func ParseDeleteWipResponse(rsp *http.Response) (*DeleteWipResponse, error) { return response, nil } -// ParseGetWipResponse parses an HTTP response from a GetWipWithResponse call -func ParseGetWipResponse(rsp *http.Response) (*GetWipResponse, error) { +// ParseGetRepositoryResponse parses an HTTP response from a GetRepositoryWithResponse call +func ParseGetRepositoryResponse(rsp *http.Response) (*GetRepositoryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetWipResponse{ + response := &GetRepositoryResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest Wip + var dest Repository if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -6337,15 +6243,15 @@ func ParseGetWipResponse(rsp *http.Response) (*GetWipResponse, error) { return response, nil } -// ParseUpdateWipResponse parses an HTTP response from a UpdateWipWithResponse call -func ParseUpdateWipResponse(rsp *http.Response) (*UpdateWipResponse, error) { +// ParseUpdateRepositoryResponse parses an HTTP response from a UpdateRepositoryWithResponse call +func ParseUpdateRepositoryResponse(rsp *http.Response) (*UpdateRepositoryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &UpdateWipResponse{ + response := &UpdateRepositoryResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -6353,22 +6259,38 @@ func ParseUpdateWipResponse(rsp *http.Response) (*UpdateWipResponse, error) { return response, nil } -// ParseGetWipChangesResponse parses an HTTP response from a GetWipChangesWithResponse call -func ParseGetWipChangesResponse(rsp *http.Response) (*GetWipChangesResponse, error) { +// ParseDeleteBranchResponse parses an HTTP response from a DeleteBranchWithResponse call +func ParseDeleteBranchResponse(rsp *http.Response) (*DeleteBranchResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetWipChangesResponse{ + response := &DeleteBranchResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetBranchResponse parses an HTTP response from a GetBranchWithResponse call +func ParseGetBranchResponse(rsp *http.Response) (*GetBranchResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetBranchResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []Change + var dest Branch if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -6379,22 +6301,22 @@ func ParseGetWipChangesResponse(rsp *http.Response) (*GetWipChangesResponse, err return response, nil } -// ParseCommitWipResponse parses an HTTP response from a CommitWipWithResponse call -func ParseCommitWipResponse(rsp *http.Response) (*CommitWipResponse, error) { +// ParseCreateBranchResponse parses an HTTP response from a CreateBranchWithResponse call +func ParseCreateBranchResponse(rsp *http.Response) (*CreateBranchResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CommitWipResponse{ + response := &CreateBranchResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest Wip + var dest BranchCreation if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -6405,22 +6327,22 @@ func ParseCommitWipResponse(rsp *http.Response) (*CommitWipResponse, error) { return response, nil } -// ParseListWipResponse parses an HTTP response from a ListWipWithResponse call -func ParseListWipResponse(rsp *http.Response) (*ListWipResponse, error) { +// ParseListBranchesResponse parses an HTTP response from a ListBranchesWithResponse call +func ParseListBranchesResponse(rsp *http.Response) (*ListBranchesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &ListWipResponse{ + response := &ListBranchesResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []Wip + var dest BranchList if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -6431,423 +6353,1470 @@ func ParseListWipResponse(rsp *http.Response) (*ListWipResponse, error) { return response, nil } -// ParseRevertWipChangesResponse parses an HTTP response from a RevertWipChangesWithResponse call -func ParseRevertWipChangesResponse(rsp *http.Response) (*RevertWipChangesResponse, error) { +// ParseGetCommitChangesResponse parses an HTTP response from a GetCommitChangesWithResponse call +func ParseGetCommitChangesResponse(rsp *http.Response) (*GetCommitChangesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &RevertWipChangesResponse{ + response := &GetCommitChangesResponse{ Body: bodyBytes, HTTPResponse: rsp, } + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []Change + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + return response, nil } -// ServerInterface represents all server handlers. -type ServerInterface interface { - // perform a login - // (POST /auth/login) - Login(ctx context.Context, w *JiaozifsResponse, r *http.Request, body LoginJSONRequestBody) - // perform a logout - // (POST /auth/logout) - Logout(ctx context.Context, w *JiaozifsResponse, r *http.Request) - // get merge request - // (GET /mergequest/{owner}/{repository}/{mrSeq}) - GetMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, mrSeq uint64) - // update merge request - // (POST /mergequest/{owner}/{repository}/{mrSeq}) - UpdateMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateMergeRequestJSONRequestBody, owner string, repository string, mrSeq uint64) - // get list of merge request in repository - // (GET /mergerequest/{owner}/{repository}) - ListMergeRequests(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params ListMergeRequestsParams) - // create merge request - // (POST /mergerequest/{owner}/{repository}) - CreateMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateMergeRequestJSONRequestBody, owner string, repository string) - // merge a mergerequest - // (POST /mergerequest/{owner}/{repository}/{mrSeq}/merge) - Merge(ctx context.Context, w *JiaozifsResponse, r *http.Request, body MergeJSONRequestBody, owner string, repository string, mrSeq uint64) - // delete object. Missing objects will not return a NotFound error. - // (DELETE /object/{owner}/{repository}) - DeleteObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteObjectParams) - // get object content - // (GET /object/{owner}/{repository}) - GetObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetObjectParams) - // check if object exists - // (HEAD /object/{owner}/{repository}) - HeadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params HeadObjectParams) +// ParseGetCommitsInRefResponse parses an HTTP response from a GetCommitsInRefWithResponse call +func ParseGetCommitsInRefResponse(rsp *http.Response) (*GetCommitsInRefResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } - // (POST /object/{owner}/{repository}) - UploadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params UploadObjectParams) - // delete repository - // (DELETE /repos/{owner}/{repository}) - DeleteRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteRepositoryParams) - // get repository - // (GET /repos/{owner}/{repository}) - GetRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) - // update repository - // (POST /repos/{owner}/{repository}) - UpdateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateRepositoryJSONRequestBody, owner string, repository string) - // delete branch - // (DELETE /repos/{owner}/{repository}/branch) - DeleteBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteBranchParams) - // get branch - // (GET /repos/{owner}/{repository}/branch) - GetBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetBranchParams) - // create branch - // (POST /repos/{owner}/{repository}/branch) - CreateBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateBranchJSONRequestBody, owner string, repository string) - // list branches - // (GET /repos/{owner}/{repository}/branches) - ListBranches(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params ListBranchesParams) - // get changes in commit - // (GET /repos/{owner}/{repository}/changes/{commit_id}) - GetCommitChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, commitId string, params GetCommitChangesParams) - // get commits in ref - // (GET /repos/{owner}/{repository}/commits) - GetCommitsInRef(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetCommitsInRefParams) - // compare two commit - // (GET /repos/{owner}/{repository}/compare/{basehead}) - CompareCommit(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, basehead string, params CompareCommitParams) - // list entries in ref - // (GET /repos/{owner}/{repository}/contents) - GetEntriesInRef(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetEntriesInRefParams) - // check if jiaozifs setup - // (GET /setup) - GetSetupState(ctx context.Context, w *JiaozifsResponse, r *http.Request) - // delete aksk - // (DELETE /users/aksk) - DeleteAksk(ctx context.Context, w *JiaozifsResponse, r *http.Request, params DeleteAkskParams) - // get aksk - // (GET /users/aksk) - GetAksk(ctx context.Context, w *JiaozifsResponse, r *http.Request, params GetAkskParams) - // create aksk - // (POST /users/aksk) - CreateAksk(ctx context.Context, w *JiaozifsResponse, r *http.Request, params CreateAkskParams) - // list aksks - // (GET /users/aksks) - ListAksks(ctx context.Context, w *JiaozifsResponse, r *http.Request, params ListAksksParams) - // refresh token for more time - // (GET /users/refreshtoken) - RefreshToken(ctx context.Context, w *JiaozifsResponse, r *http.Request) - // perform user registration - // (POST /users/register) - Register(ctx context.Context, w *JiaozifsResponse, r *http.Request, body RegisterJSONRequestBody) - // list repository - // (GET /users/repos) - ListRepositoryOfAuthenticatedUser(ctx context.Context, w *JiaozifsResponse, r *http.Request, params ListRepositoryOfAuthenticatedUserParams) - // create repository - // (POST /users/repos) - CreateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateRepositoryJSONRequestBody) - // get information of the currently logged-in user - // (GET /users/user) - GetUserInfo(ctx context.Context, w *JiaozifsResponse, r *http.Request) - // list repository in specific owner - // (GET /users/{owner}/repos) - ListRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, params ListRepositoryParams) - // return program and runtime version - // (GET /version) - GetVersion(ctx context.Context, w *JiaozifsResponse, r *http.Request) - // remove working in process - // (DELETE /wip/{owner}/{repository}) - DeleteWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteWipParams) - // get working in process - // (GET /wip/{owner}/{repository}) - GetWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetWipParams) - // update wip - // (POST /wip/{owner}/{repository}) - UpdateWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateWipJSONRequestBody, owner string, repository string, params UpdateWipParams) - // get working in process changes - // (GET /wip/{owner}/{repository}/changes) - GetWipChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetWipChangesParams) - // commit working in process to branch - // (POST /wip/{owner}/{repository}/commit) - CommitWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params CommitWipParams) - // list wip in specific project and user - // (GET /wip/{owner}/{repository}/list) - ListWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) - // revert changes in working in process, empty path will revert all - // (POST /wip/{owner}/{repository}/revert) - RevertWipChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params RevertWipChangesParams) -} + response := &GetCommitsInRefResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } -// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []Commit + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest -type Unimplemented struct{} + } -// perform a login -// (POST /auth/login) -func (_ Unimplemented) Login(ctx context.Context, w *JiaozifsResponse, r *http.Request, body LoginJSONRequestBody) { - w.WriteHeader(http.StatusNotImplemented) + return response, nil } -// perform a logout -// (POST /auth/logout) -func (_ Unimplemented) Logout(ctx context.Context, w *JiaozifsResponse, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) -} +// ParseCompareCommitResponse parses an HTTP response from a CompareCommitWithResponse call +func ParseCompareCommitResponse(rsp *http.Response) (*CompareCommitResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } -// get merge request -// (GET /mergequest/{owner}/{repository}/{mrSeq}) -func (_ Unimplemented) GetMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, mrSeq uint64) { - w.WriteHeader(http.StatusNotImplemented) -} + response := &CompareCommitResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } -// update merge request -// (POST /mergequest/{owner}/{repository}/{mrSeq}) -func (_ Unimplemented) UpdateMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateMergeRequestJSONRequestBody, owner string, repository string, mrSeq uint64) { - w.WriteHeader(http.StatusNotImplemented) -} + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []Change + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest -// get list of merge request in repository -// (GET /mergerequest/{owner}/{repository}) -func (_ Unimplemented) ListMergeRequests(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params ListMergeRequestsParams) { - w.WriteHeader(http.StatusNotImplemented) -} + } -// create merge request -// (POST /mergerequest/{owner}/{repository}) -func (_ Unimplemented) CreateMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateMergeRequestJSONRequestBody, owner string, repository string) { - w.WriteHeader(http.StatusNotImplemented) + return response, nil } -// merge a mergerequest -// (POST /mergerequest/{owner}/{repository}/{mrSeq}/merge) -func (_ Unimplemented) Merge(ctx context.Context, w *JiaozifsResponse, r *http.Request, body MergeJSONRequestBody, owner string, repository string, mrSeq uint64) { - w.WriteHeader(http.StatusNotImplemented) -} +// ParseGetEntriesInRefResponse parses an HTTP response from a GetEntriesInRefWithResponse call +func ParseGetEntriesInRefResponse(rsp *http.Response) (*GetEntriesInRefResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } -// delete object. Missing objects will not return a NotFound error. -// (DELETE /object/{owner}/{repository}) -func (_ Unimplemented) DeleteObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteObjectParams) { - w.WriteHeader(http.StatusNotImplemented) -} + response := &GetEntriesInRefResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } -// get object content -// (GET /object/{owner}/{repository}) -func (_ Unimplemented) GetObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetObjectParams) { - w.WriteHeader(http.StatusNotImplemented) -} + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []FullTreeEntry + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest -// check if object exists -// (HEAD /object/{owner}/{repository}) -func (_ Unimplemented) HeadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params HeadObjectParams) { - w.WriteHeader(http.StatusNotImplemented) -} + } -// (POST /object/{owner}/{repository}) -func (_ Unimplemented) UploadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params UploadObjectParams) { - w.WriteHeader(http.StatusNotImplemented) + return response, nil } -// delete repository -// (DELETE /repos/{owner}/{repository}) -func (_ Unimplemented) DeleteRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteRepositoryParams) { - w.WriteHeader(http.StatusNotImplemented) -} +// ParseRevokeMemberResponse parses an HTTP response from a RevokeMemberWithResponse call +func ParseRevokeMemberResponse(rsp *http.Response) (*RevokeMemberResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } -// get repository -// (GET /repos/{owner}/{repository}) -func (_ Unimplemented) GetRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) { - w.WriteHeader(http.StatusNotImplemented) -} + response := &RevokeMemberResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } -// update repository -// (POST /repos/{owner}/{repository}) -func (_ Unimplemented) UpdateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateRepositoryJSONRequestBody, owner string, repository string) { - w.WriteHeader(http.StatusNotImplemented) + return response, nil } -// delete branch -// (DELETE /repos/{owner}/{repository}/branch) -func (_ Unimplemented) DeleteBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteBranchParams) { - w.WriteHeader(http.StatusNotImplemented) -} +// ParseUpdateMemberGroupResponse parses an HTTP response from a UpdateMemberGroupWithResponse call +func ParseUpdateMemberGroupResponse(rsp *http.Response) (*UpdateMemberGroupResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } -// get branch -// (GET /repos/{owner}/{repository}/branch) -func (_ Unimplemented) GetBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetBranchParams) { - w.WriteHeader(http.StatusNotImplemented) -} + response := &UpdateMemberGroupResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } -// create branch -// (POST /repos/{owner}/{repository}/branch) -func (_ Unimplemented) CreateBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateBranchJSONRequestBody, owner string, repository string) { - w.WriteHeader(http.StatusNotImplemented) + return response, nil } -// list branches -// (GET /repos/{owner}/{repository}/branches) -func (_ Unimplemented) ListBranches(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params ListBranchesParams) { - w.WriteHeader(http.StatusNotImplemented) -} +// ParseInviteMemberResponse parses an HTTP response from a InviteMemberWithResponse call +func ParseInviteMemberResponse(rsp *http.Response) (*InviteMemberResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } -// get changes in commit -// (GET /repos/{owner}/{repository}/changes/{commit_id}) -func (_ Unimplemented) GetCommitChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, commitId string, params GetCommitChangesParams) { - w.WriteHeader(http.StatusNotImplemented) -} + response := &InviteMemberResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } -// get commits in ref -// (GET /repos/{owner}/{repository}/commits) -func (_ Unimplemented) GetCommitsInRef(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetCommitsInRefParams) { - w.WriteHeader(http.StatusNotImplemented) + return response, nil } -// compare two commit -// (GET /repos/{owner}/{repository}/compare/{basehead}) -func (_ Unimplemented) CompareCommit(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, basehead string, params CompareCommitParams) { - w.WriteHeader(http.StatusNotImplemented) -} +// ParseListMembersResponse parses an HTTP response from a ListMembersWithResponse call +func ParseListMembersResponse(rsp *http.Response) (*ListMembersResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } -// list entries in ref -// (GET /repos/{owner}/{repository}/contents) -func (_ Unimplemented) GetEntriesInRef(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetEntriesInRefParams) { - w.WriteHeader(http.StatusNotImplemented) + response := &ListMembersResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []Member + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil } -// check if jiaozifs setup -// (GET /setup) -func (_ Unimplemented) GetSetupState(ctx context.Context, w *JiaozifsResponse, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) +// ParseListMergeRequestsResponse parses an HTTP response from a ListMergeRequestsWithResponse call +func ParseListMergeRequestsResponse(rsp *http.Response) (*ListMergeRequestsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListMergeRequestsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest MergeRequestList + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil } -// delete aksk -// (DELETE /users/aksk) -func (_ Unimplemented) DeleteAksk(ctx context.Context, w *JiaozifsResponse, r *http.Request, params DeleteAkskParams) { - w.WriteHeader(http.StatusNotImplemented) +// ParseCreateMergeRequestResponse parses an HTTP response from a CreateMergeRequestWithResponse call +func ParseCreateMergeRequestResponse(rsp *http.Response) (*CreateMergeRequestResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateMergeRequestResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest MergeRequest + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil } -// get aksk -// (GET /users/aksk) -func (_ Unimplemented) GetAksk(ctx context.Context, w *JiaozifsResponse, r *http.Request, params GetAkskParams) { - w.WriteHeader(http.StatusNotImplemented) -} +// ParseGetMergeRequestResponse parses an HTTP response from a GetMergeRequestWithResponse call +func ParseGetMergeRequestResponse(rsp *http.Response) (*GetMergeRequestResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetMergeRequestResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest MergeRequestFullState + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseUpdateMergeRequestResponse parses an HTTP response from a UpdateMergeRequestWithResponse call +func ParseUpdateMergeRequestResponse(rsp *http.Response) (*UpdateMergeRequestResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UpdateMergeRequestResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseMergeResponse parses an HTTP response from a MergeWithResponse call +func ParseMergeResponse(rsp *http.Response) (*MergeResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &MergeResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []Commit + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseGetSetupStateResponse parses an HTTP response from a GetSetupStateWithResponse call +func ParseGetSetupStateResponse(rsp *http.Response) (*GetSetupStateResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetSetupStateResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest SetupState + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseDeleteAkskResponse parses an HTTP response from a DeleteAkskWithResponse call +func ParseDeleteAkskResponse(rsp *http.Response) (*DeleteAkskResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DeleteAkskResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetAkskResponse parses an HTTP response from a GetAkskWithResponse call +func ParseGetAkskResponse(rsp *http.Response) (*GetAkskResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetAkskResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Aksk + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseCreateAkskResponse parses an HTTP response from a CreateAkskWithResponse call +func ParseCreateAkskResponse(rsp *http.Response) (*CreateAkskResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateAkskResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest Aksk + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil +} + +// ParseListAksksResponse parses an HTTP response from a ListAksksWithResponse call +func ParseListAksksResponse(rsp *http.Response) (*ListAksksResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListAksksResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest AkskList + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseRefreshTokenResponse parses an HTTP response from a RefreshTokenWithResponse call +func ParseRefreshTokenResponse(rsp *http.Response) (*RefreshTokenResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &RefreshTokenResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest AuthenticationToken + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseRegisterResponse parses an HTTP response from a RegisterWithResponse call +func ParseRegisterResponse(rsp *http.Response) (*RegisterResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &RegisterResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest UserInfo + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil +} + +// ParseListRepositoryOfAuthenticatedUserResponse parses an HTTP response from a ListRepositoryOfAuthenticatedUserWithResponse call +func ParseListRepositoryOfAuthenticatedUserResponse(rsp *http.Response) (*ListRepositoryOfAuthenticatedUserResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListRepositoryOfAuthenticatedUserResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest RepositoryList + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseCreateRepositoryResponse parses an HTTP response from a CreateRepositoryWithResponse call +func ParseCreateRepositoryResponse(rsp *http.Response) (*CreateRepositoryResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateRepositoryResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest Repository + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil +} + +// ParseGetUserInfoResponse parses an HTTP response from a GetUserInfoWithResponse call +func ParseGetUserInfoResponse(rsp *http.Response) (*GetUserInfoResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetUserInfoResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest UserInfo + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseListRepositoryResponse parses an HTTP response from a ListRepositoryWithResponse call +func ParseListRepositoryResponse(rsp *http.Response) (*ListRepositoryResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListRepositoryResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest RepositoryList + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseGetVersionResponse parses an HTTP response from a GetVersionWithResponse call +func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetVersionResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest VersionResult + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseDeleteWipResponse parses an HTTP response from a DeleteWipWithResponse call +func ParseDeleteWipResponse(rsp *http.Response) (*DeleteWipResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DeleteWipResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetWipResponse parses an HTTP response from a GetWipWithResponse call +func ParseGetWipResponse(rsp *http.Response) (*GetWipResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetWipResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Wip + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseUpdateWipResponse parses an HTTP response from a UpdateWipWithResponse call +func ParseUpdateWipResponse(rsp *http.Response) (*UpdateWipResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UpdateWipResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetWipChangesResponse parses an HTTP response from a GetWipChangesWithResponse call +func ParseGetWipChangesResponse(rsp *http.Response) (*GetWipChangesResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetWipChangesResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []Change + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseCommitWipResponse parses an HTTP response from a CommitWipWithResponse call +func ParseCommitWipResponse(rsp *http.Response) (*CommitWipResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CommitWipResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest Wip + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil +} + +// ParseListWipResponse parses an HTTP response from a ListWipWithResponse call +func ParseListWipResponse(rsp *http.Response) (*ListWipResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListWipResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []Wip + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseRevertWipChangesResponse parses an HTTP response from a RevertWipChangesWithResponse call +func ParseRevertWipChangesResponse(rsp *http.Response) (*RevertWipChangesResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &RevertWipChangesResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // perform a login + // (POST /auth/login) + Login(ctx context.Context, w *JiaozifsResponse, r *http.Request, body LoginJSONRequestBody) + // perform a logout + // (POST /auth/logout) + Logout(ctx context.Context, w *JiaozifsResponse, r *http.Request) + // list groups for repo + // (GET /groups/repo) + ListRepoGroup(ctx context.Context, w *JiaozifsResponse, r *http.Request) + // delete object. Missing objects will not return a NotFound error. + // (DELETE /object/{owner}/{repository}) + DeleteObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteObjectParams) + // get object content + // (GET /object/{owner}/{repository}) + GetObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetObjectParams) + // check if object exists + // (HEAD /object/{owner}/{repository}) + HeadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params HeadObjectParams) + + // (POST /object/{owner}/{repository}) + UploadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params UploadObjectParams) + // delete repository + // (DELETE /repos/{owner}/{repository}) + DeleteRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteRepositoryParams) + // get repository + // (GET /repos/{owner}/{repository}) + GetRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) + // update repository + // (POST /repos/{owner}/{repository}) + UpdateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateRepositoryJSONRequestBody, owner string, repository string) + // delete branch + // (DELETE /repos/{owner}/{repository}/branch) + DeleteBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteBranchParams) + // get branch + // (GET /repos/{owner}/{repository}/branch) + GetBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetBranchParams) + // create branch + // (POST /repos/{owner}/{repository}/branch) + CreateBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateBranchJSONRequestBody, owner string, repository string) + // list branches + // (GET /repos/{owner}/{repository}/branches) + ListBranches(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params ListBranchesParams) + // get changes in commit + // (GET /repos/{owner}/{repository}/changes/{commit_id}) + GetCommitChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, commitId string, params GetCommitChangesParams) + // get commits in ref + // (GET /repos/{owner}/{repository}/commits) + GetCommitsInRef(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetCommitsInRefParams) + // compare two commit + // (GET /repos/{owner}/{repository}/compare/{basehead}) + CompareCommit(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, basehead string, params CompareCommitParams) + // list entries in ref + // (GET /repos/{owner}/{repository}/contents) + GetEntriesInRef(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetEntriesInRefParams) + // Revoke member in repository + // (DELETE /repos/{owner}/{repository}/member) + RevokeMember(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params RevokeMemberParams) + // update member by user id and change group role + // (POST /repos/{owner}/{repository}/member) + UpdateMemberGroup(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params UpdateMemberGroupParams) + // invite member + // (POST /repos/{owner}/{repository}/member/invite) + InviteMember(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params InviteMemberParams) + // get list of members in repository + // (GET /repos/{owner}/{repository}/members) + ListMembers(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) + // get list of merge request in repository + // (GET /repos/{owner}/{repository}/mergerequest) + ListMergeRequests(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params ListMergeRequestsParams) + // create merge request + // (POST /repos/{owner}/{repository}/mergerequest) + CreateMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateMergeRequestJSONRequestBody, owner string, repository string) + // get merge request + // (GET /repos/{owner}/{repository}/mergerequest/{mrSeq}) + GetMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, mrSeq uint64) + // update merge request + // (POST /repos/{owner}/{repository}/mergerequest/{mrSeq}) + UpdateMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateMergeRequestJSONRequestBody, owner string, repository string, mrSeq uint64) + // merge a mergerequest + // (POST /repos/{owner}/{repository}/mergerequest/{mrSeq}/merge) + Merge(ctx context.Context, w *JiaozifsResponse, r *http.Request, body MergeJSONRequestBody, owner string, repository string, mrSeq uint64) + // check if jiaozifs setup + // (GET /setup) + GetSetupState(ctx context.Context, w *JiaozifsResponse, r *http.Request) + // delete aksk + // (DELETE /users/aksk) + DeleteAksk(ctx context.Context, w *JiaozifsResponse, r *http.Request, params DeleteAkskParams) + // get aksk + // (GET /users/aksk) + GetAksk(ctx context.Context, w *JiaozifsResponse, r *http.Request, params GetAkskParams) + // create aksk + // (POST /users/aksk) + CreateAksk(ctx context.Context, w *JiaozifsResponse, r *http.Request, params CreateAkskParams) + // list aksks + // (GET /users/aksks) + ListAksks(ctx context.Context, w *JiaozifsResponse, r *http.Request, params ListAksksParams) + // refresh token for more time + // (GET /users/refreshtoken) + RefreshToken(ctx context.Context, w *JiaozifsResponse, r *http.Request) + // perform user registration + // (POST /users/register) + Register(ctx context.Context, w *JiaozifsResponse, r *http.Request, body RegisterJSONRequestBody) + // list repository + // (GET /users/repos) + ListRepositoryOfAuthenticatedUser(ctx context.Context, w *JiaozifsResponse, r *http.Request, params ListRepositoryOfAuthenticatedUserParams) + // create repository + // (POST /users/repos) + CreateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateRepositoryJSONRequestBody) + // get information of the currently logged-in user + // (GET /users/user) + GetUserInfo(ctx context.Context, w *JiaozifsResponse, r *http.Request) + // list repository in specific owner + // (GET /users/{owner}/repos) + ListRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, params ListRepositoryParams) + // return program and runtime version + // (GET /version) + GetVersion(ctx context.Context, w *JiaozifsResponse, r *http.Request) + // remove working in process + // (DELETE /wip/{owner}/{repository}) + DeleteWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteWipParams) + // get working in process + // (GET /wip/{owner}/{repository}) + GetWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetWipParams) + // update wip + // (POST /wip/{owner}/{repository}) + UpdateWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateWipJSONRequestBody, owner string, repository string, params UpdateWipParams) + // get working in process changes + // (GET /wip/{owner}/{repository}/changes) + GetWipChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetWipChangesParams) + // commit working in process to branch + // (POST /wip/{owner}/{repository}/commit) + CommitWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params CommitWipParams) + // list wip in specific project and user + // (GET /wip/{owner}/{repository}/list) + ListWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) + // revert changes in working in process, empty path will revert all + // (POST /wip/{owner}/{repository}/revert) + RevertWipChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params RevertWipChangesParams) +} + +// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. + +type Unimplemented struct{} + +// perform a login +// (POST /auth/login) +func (_ Unimplemented) Login(ctx context.Context, w *JiaozifsResponse, r *http.Request, body LoginJSONRequestBody) { + w.WriteHeader(http.StatusNotImplemented) +} + +// perform a logout +// (POST /auth/logout) +func (_ Unimplemented) Logout(ctx context.Context, w *JiaozifsResponse, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// list groups for repo +// (GET /groups/repo) +func (_ Unimplemented) ListRepoGroup(ctx context.Context, w *JiaozifsResponse, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// delete object. Missing objects will not return a NotFound error. +// (DELETE /object/{owner}/{repository}) +func (_ Unimplemented) DeleteObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteObjectParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get object content +// (GET /object/{owner}/{repository}) +func (_ Unimplemented) GetObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetObjectParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// check if object exists +// (HEAD /object/{owner}/{repository}) +func (_ Unimplemented) HeadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params HeadObjectParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// (POST /object/{owner}/{repository}) +func (_ Unimplemented) UploadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params UploadObjectParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// delete repository +// (DELETE /repos/{owner}/{repository}) +func (_ Unimplemented) DeleteRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteRepositoryParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get repository +// (GET /repos/{owner}/{repository}) +func (_ Unimplemented) GetRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// update repository +// (POST /repos/{owner}/{repository}) +func (_ Unimplemented) UpdateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateRepositoryJSONRequestBody, owner string, repository string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// delete branch +// (DELETE /repos/{owner}/{repository}/branch) +func (_ Unimplemented) DeleteBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteBranchParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get branch +// (GET /repos/{owner}/{repository}/branch) +func (_ Unimplemented) GetBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetBranchParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// create branch +// (POST /repos/{owner}/{repository}/branch) +func (_ Unimplemented) CreateBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateBranchJSONRequestBody, owner string, repository string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// list branches +// (GET /repos/{owner}/{repository}/branches) +func (_ Unimplemented) ListBranches(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params ListBranchesParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get changes in commit +// (GET /repos/{owner}/{repository}/changes/{commit_id}) +func (_ Unimplemented) GetCommitChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, commitId string, params GetCommitChangesParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get commits in ref +// (GET /repos/{owner}/{repository}/commits) +func (_ Unimplemented) GetCommitsInRef(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetCommitsInRefParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// compare two commit +// (GET /repos/{owner}/{repository}/compare/{basehead}) +func (_ Unimplemented) CompareCommit(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, basehead string, params CompareCommitParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// list entries in ref +// (GET /repos/{owner}/{repository}/contents) +func (_ Unimplemented) GetEntriesInRef(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetEntriesInRefParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Revoke member in repository +// (DELETE /repos/{owner}/{repository}/member) +func (_ Unimplemented) RevokeMember(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params RevokeMemberParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// update member by user id and change group role +// (POST /repos/{owner}/{repository}/member) +func (_ Unimplemented) UpdateMemberGroup(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params UpdateMemberGroupParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// invite member +// (POST /repos/{owner}/{repository}/member/invite) +func (_ Unimplemented) InviteMember(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params InviteMemberParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get list of members in repository +// (GET /repos/{owner}/{repository}/members) +func (_ Unimplemented) ListMembers(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get list of merge request in repository +// (GET /repos/{owner}/{repository}/mergerequest) +func (_ Unimplemented) ListMergeRequests(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params ListMergeRequestsParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// create merge request +// (POST /repos/{owner}/{repository}/mergerequest) +func (_ Unimplemented) CreateMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateMergeRequestJSONRequestBody, owner string, repository string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get merge request +// (GET /repos/{owner}/{repository}/mergerequest/{mrSeq}) +func (_ Unimplemented) GetMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, mrSeq uint64) { + w.WriteHeader(http.StatusNotImplemented) +} + +// update merge request +// (POST /repos/{owner}/{repository}/mergerequest/{mrSeq}) +func (_ Unimplemented) UpdateMergeRequest(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateMergeRequestJSONRequestBody, owner string, repository string, mrSeq uint64) { + w.WriteHeader(http.StatusNotImplemented) +} + +// merge a mergerequest +// (POST /repos/{owner}/{repository}/mergerequest/{mrSeq}/merge) +func (_ Unimplemented) Merge(ctx context.Context, w *JiaozifsResponse, r *http.Request, body MergeJSONRequestBody, owner string, repository string, mrSeq uint64) { + w.WriteHeader(http.StatusNotImplemented) +} + +// check if jiaozifs setup +// (GET /setup) +func (_ Unimplemented) GetSetupState(ctx context.Context, w *JiaozifsResponse, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// delete aksk +// (DELETE /users/aksk) +func (_ Unimplemented) DeleteAksk(ctx context.Context, w *JiaozifsResponse, r *http.Request, params DeleteAkskParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get aksk +// (GET /users/aksk) +func (_ Unimplemented) GetAksk(ctx context.Context, w *JiaozifsResponse, r *http.Request, params GetAkskParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// create aksk +// (POST /users/aksk) +func (_ Unimplemented) CreateAksk(ctx context.Context, w *JiaozifsResponse, r *http.Request, params CreateAkskParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// list aksks +// (GET /users/aksks) +func (_ Unimplemented) ListAksks(ctx context.Context, w *JiaozifsResponse, r *http.Request, params ListAksksParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// refresh token for more time +// (GET /users/refreshtoken) +func (_ Unimplemented) RefreshToken(ctx context.Context, w *JiaozifsResponse, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// perform user registration +// (POST /users/register) +func (_ Unimplemented) Register(ctx context.Context, w *JiaozifsResponse, r *http.Request, body RegisterJSONRequestBody) { + w.WriteHeader(http.StatusNotImplemented) +} + +// list repository +// (GET /users/repos) +func (_ Unimplemented) ListRepositoryOfAuthenticatedUser(ctx context.Context, w *JiaozifsResponse, r *http.Request, params ListRepositoryOfAuthenticatedUserParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// create repository +// (POST /users/repos) +func (_ Unimplemented) CreateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateRepositoryJSONRequestBody) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get information of the currently logged-in user +// (GET /users/user) +func (_ Unimplemented) GetUserInfo(ctx context.Context, w *JiaozifsResponse, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// list repository in specific owner +// (GET /users/{owner}/repos) +func (_ Unimplemented) ListRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, params ListRepositoryParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// return program and runtime version +// (GET /version) +func (_ Unimplemented) GetVersion(ctx context.Context, w *JiaozifsResponse, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// remove working in process +// (DELETE /wip/{owner}/{repository}) +func (_ Unimplemented) DeleteWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteWipParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get working in process +// (GET /wip/{owner}/{repository}) +func (_ Unimplemented) GetWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetWipParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// update wip +// (POST /wip/{owner}/{repository}) +func (_ Unimplemented) UpdateWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateWipJSONRequestBody, owner string, repository string, params UpdateWipParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get working in process changes +// (GET /wip/{owner}/{repository}/changes) +func (_ Unimplemented) GetWipChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetWipChangesParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// commit working in process to branch +// (POST /wip/{owner}/{repository}/commit) +func (_ Unimplemented) CommitWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params CommitWipParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// list wip in specific project and user +// (GET /wip/{owner}/{repository}/list) +func (_ Unimplemented) ListWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// revert changes in working in process, empty path will revert all +// (POST /wip/{owner}/{repository}/revert) +func (_ Unimplemented) RevertWipChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params RevertWipChangesParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// Login operation middleware +func (siw *ServerInterfaceWrapper) Login(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + // ------------- Body parse ------------- + var body LoginJSONRequestBody + parseBody := r.ContentLength != 0 + if parseBody { + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "Error unmarshalling body 'Login' as JSON", http.StatusBadRequest) + return + } + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.Login(r.Context(), &JiaozifsResponse{w}, r, body) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// Logout operation middleware +func (siw *ServerInterfaceWrapper) Logout(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.Logout(r.Context(), &JiaozifsResponse{w}, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// ListRepoGroup operation middleware +func (siw *ServerInterfaceWrapper) ListRepoGroup(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListRepoGroup(r.Context(), &JiaozifsResponse{w}, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// DeleteObject operation middleware +func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params DeleteObjectParams + + // ------------- Required query parameter "refName" ------------- + + if paramValue := r.URL.Query().Get("refName"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) + return + } + + // ------------- Required query parameter "path" ------------- + + if paramValue := r.URL.Query().Get("path"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteObject(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// GetObject operation middleware +func (siw *ServerInterfaceWrapper) GetObject(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) -// create aksk -// (POST /users/aksk) -func (_ Unimplemented) CreateAksk(ctx context.Context, w *JiaozifsResponse, r *http.Request, params CreateAkskParams) { - w.WriteHeader(http.StatusNotImplemented) -} + ctx = context.WithValue(ctx, SignatureScopes, []string{}) -// list aksks -// (GET /users/aksks) -func (_ Unimplemented) ListAksks(ctx context.Context, w *JiaozifsResponse, r *http.Request, params ListAksksParams) { - w.WriteHeader(http.StatusNotImplemented) -} + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) -// refresh token for more time -// (GET /users/refreshtoken) -func (_ Unimplemented) RefreshToken(ctx context.Context, w *JiaozifsResponse, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) -} + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) -// perform user registration -// (POST /users/register) -func (_ Unimplemented) Register(ctx context.Context, w *JiaozifsResponse, r *http.Request, body RegisterJSONRequestBody) { - w.WriteHeader(http.StatusNotImplemented) -} + ctx = context.WithValue(ctx, TimestampScopes, []string{}) -// list repository -// (GET /users/repos) -func (_ Unimplemented) ListRepositoryOfAuthenticatedUser(ctx context.Context, w *JiaozifsResponse, r *http.Request, params ListRepositoryOfAuthenticatedUserParams) { - w.WriteHeader(http.StatusNotImplemented) -} + // Parameter object where we will unmarshal all parameters from the context + var params GetObjectParams -// create repository -// (POST /users/repos) -func (_ Unimplemented) CreateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateRepositoryJSONRequestBody) { - w.WriteHeader(http.StatusNotImplemented) -} + // ------------- Required query parameter "type" ------------- -// get information of the currently logged-in user -// (GET /users/user) -func (_ Unimplemented) GetUserInfo(ctx context.Context, w *JiaozifsResponse, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) -} + if paramValue := r.URL.Query().Get("type"); paramValue != "" { -// list repository in specific owner -// (GET /users/{owner}/repos) -func (_ Unimplemented) ListRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, params ListRepositoryParams) { - w.WriteHeader(http.StatusNotImplemented) -} + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "type"}) + return + } -// return program and runtime version -// (GET /version) -func (_ Unimplemented) GetVersion(ctx context.Context, w *JiaozifsResponse, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) -} + err = runtime.BindQueryParameter("form", true, true, "type", r.URL.Query(), ¶ms.Type) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "type", Err: err}) + return + } -// remove working in process -// (DELETE /wip/{owner}/{repository}) -func (_ Unimplemented) DeleteWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteWipParams) { - w.WriteHeader(http.StatusNotImplemented) -} + // ------------- Required query parameter "refName" ------------- -// get working in process -// (GET /wip/{owner}/{repository}) -func (_ Unimplemented) GetWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetWipParams) { - w.WriteHeader(http.StatusNotImplemented) -} + if paramValue := r.URL.Query().Get("refName"); paramValue != "" { -// update wip -// (POST /wip/{owner}/{repository}) -func (_ Unimplemented) UpdateWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateWipJSONRequestBody, owner string, repository string, params UpdateWipParams) { - w.WriteHeader(http.StatusNotImplemented) -} + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) + return + } -// get working in process changes -// (GET /wip/{owner}/{repository}/changes) -func (_ Unimplemented) GetWipChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetWipChangesParams) { - w.WriteHeader(http.StatusNotImplemented) -} + err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) + return + } -// commit working in process to branch -// (POST /wip/{owner}/{repository}/commit) -func (_ Unimplemented) CommitWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params CommitWipParams) { - w.WriteHeader(http.StatusNotImplemented) -} + // ------------- Required query parameter "path" ------------- -// list wip in specific project and user -// (GET /wip/{owner}/{repository}/list) -func (_ Unimplemented) ListWip(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string) { - w.WriteHeader(http.StatusNotImplemented) -} + if paramValue := r.URL.Query().Get("path"); paramValue != "" { -// revert changes in working in process, empty path will revert all -// (POST /wip/{owner}/{repository}/revert) -func (_ Unimplemented) RevertWipChanges(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params RevertWipChangesParams) { - w.WriteHeader(http.StatusNotImplemented) -} + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) + return + } -// ServerInterfaceWrapper converts contexts to parameters. -type ServerInterfaceWrapper struct { - Handler ServerInterface - HandlerMiddlewares []MiddlewareFunc - ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) -} + err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } -type MiddlewareFunc func(http.Handler) http.Handler + headers := r.Header -// Login operation middleware -func (siw *ServerInterfaceWrapper) Login(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + // ------------- Optional header parameter "Range" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("Range")]; found { + var Range string + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Range", Count: n}) + return + } - // ------------- Body parse ------------- - var body LoginJSONRequestBody - parseBody := r.ContentLength != 0 - if parseBody { - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - http.Error(w, "Error unmarshalling body 'Login' as JSON", http.StatusBadRequest) + err = runtime.BindStyledParameterWithOptions("simple", "Range", valueList[0], &Range, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Range", Err: err}) return } + + params.Range = &Range + } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.Login(r.Context(), &JiaozifsResponse{w}, r, body) + siw.Handler.GetObject(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -6857,10 +7826,30 @@ func (siw *ServerInterfaceWrapper) Login(w http.ResponseWriter, r *http.Request) handler.ServeHTTP(w, r.WithContext(ctx)) } -// Logout operation middleware -func (siw *ServerInterfaceWrapper) Logout(w http.ResponseWriter, r *http.Request) { +// HeadObject operation middleware +func (siw *ServerInterfaceWrapper) HeadObject(w http.ResponseWriter, r *http.Request) { ctx := r.Context() + var err error + + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) ctx = context.WithValue(ctx, Basic_authScopes, []string{}) @@ -6877,8 +7866,77 @@ func (siw *ServerInterfaceWrapper) Logout(w http.ResponseWriter, r *http.Request ctx = context.WithValue(ctx, TimestampScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context + var params HeadObjectParams + + // ------------- Required query parameter "type" ------------- + + if paramValue := r.URL.Query().Get("type"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "type"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "type", r.URL.Query(), ¶ms.Type) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "type", Err: err}) + return + } + + // ------------- Required query parameter "refName" ------------- + + if paramValue := r.URL.Query().Get("refName"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) + return + } + + // ------------- Required query parameter "path" ------------- + + if paramValue := r.URL.Query().Get("path"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + headers := r.Header + + // ------------- Optional header parameter "Range" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("Range")]; found { + var Range string + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Range", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "Range", valueList[0], &Range, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Range", Err: err}) + return + } + + params.Range = &Range + + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.Logout(r.Context(), &JiaozifsResponse{w}, r) + siw.Handler.HeadObject(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -6888,8 +7946,8 @@ func (siw *ServerInterfaceWrapper) Logout(w http.ResponseWriter, r *http.Request handler.ServeHTTP(w, r.WithContext(ctx)) } -// GetMergeRequest operation middleware -func (siw *ServerInterfaceWrapper) GetMergeRequest(w http.ResponseWriter, r *http.Request) { +// UploadObject operation middleware +func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -6912,15 +7970,6 @@ func (siw *ServerInterfaceWrapper) GetMergeRequest(w http.ResponseWriter, r *htt return } - // ------------- Path parameter "mrSeq" ------------- - var mrSeq uint64 - - err = runtime.BindStyledParameterWithOptions("simple", "mrSeq", chi.URLParam(r, "mrSeq"), &mrSeq, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "mrSeq", Err: err}) - return - } - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) ctx = context.WithValue(ctx, Basic_authScopes, []string{}) @@ -6937,78 +7986,41 @@ func (siw *ServerInterfaceWrapper) GetMergeRequest(w http.ResponseWriter, r *htt ctx = context.WithValue(ctx, TimestampScopes, []string{}) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetMergeRequest(r.Context(), &JiaozifsResponse{w}, r, owner, repository, mrSeq) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r.WithContext(ctx)) -} + // Parameter object where we will unmarshal all parameters from the context + var params UploadObjectParams -// UpdateMergeRequest operation middleware -func (siw *ServerInterfaceWrapper) UpdateMergeRequest(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + // ------------- Required query parameter "refName" ------------- - var err error + if paramValue := r.URL.Query().Get("refName"); paramValue != "" { - // ------------- Body parse ------------- - var body UpdateMergeRequestJSONRequestBody - parseBody := true - if parseBody { - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - http.Error(w, "Error unmarshalling body 'UpdateMergeRequest' as JSON", http.StatusBadRequest) - return - } + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) + return } - // ------------- Path parameter "owner" ------------- - var owner string - - err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) return } - // ------------- Path parameter "repository" ------------- - var repository string + // ------------- Required query parameter "path" ------------- - err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + if paramValue := r.URL.Query().Get("path"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) return } - // ------------- Path parameter "mrSeq" ------------- - var mrSeq uint64 - - err = runtime.BindStyledParameterWithOptions("simple", "mrSeq", chi.URLParam(r, "mrSeq"), &mrSeq, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "mrSeq", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) return } - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - - ctx = context.WithValue(ctx, Basic_authScopes, []string{}) - - ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - - ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) - - ctx = context.WithValue(ctx, SignatureScopes, []string{}) - - ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - - ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) - - ctx = context.WithValue(ctx, TimestampScopes, []string{}) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.UpdateMergeRequest(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository, mrSeq) + siw.Handler.UploadObject(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -7018,8 +8030,8 @@ func (siw *ServerInterfaceWrapper) UpdateMergeRequest(w http.ResponseWriter, r * handler.ServeHTTP(w, r.WithContext(ctx)) } -// ListMergeRequests operation middleware -func (siw *ServerInterfaceWrapper) ListMergeRequests(w http.ResponseWriter, r *http.Request) { +// DeleteRepository operation middleware +func (siw *ServerInterfaceWrapper) DeleteRepository(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -7059,34 +8071,18 @@ func (siw *ServerInterfaceWrapper) ListMergeRequests(w http.ResponseWriter, r *h ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context - var params ListMergeRequestsParams - - // ------------- Optional query parameter "after" ------------- - - err = runtime.BindQueryParameter("form", true, false, "after", r.URL.Query(), ¶ms.After) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "after", Err: err}) - return - } - - // ------------- Optional query parameter "amount" ------------- - - err = runtime.BindQueryParameter("form", true, false, "amount", r.URL.Query(), ¶ms.Amount) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "amount", Err: err}) - return - } + var params DeleteRepositoryParams - // ------------- Optional query parameter "state" ------------- + // ------------- Optional query parameter "is_clean_data" ------------- - err = runtime.BindQueryParameter("form", true, false, "state", r.URL.Query(), ¶ms.State) + err = runtime.BindQueryParameter("form", true, false, "is_clean_data", r.URL.Query(), ¶ms.IsCleanData) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "state", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "is_clean_data", Err: err}) return } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.ListMergeRequests(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + siw.Handler.DeleteRepository(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -7096,22 +8092,12 @@ func (siw *ServerInterfaceWrapper) ListMergeRequests(w http.ResponseWriter, r *h handler.ServeHTTP(w, r.WithContext(ctx)) } -// CreateMergeRequest operation middleware -func (siw *ServerInterfaceWrapper) CreateMergeRequest(w http.ResponseWriter, r *http.Request) { +// GetRepository operation middleware +func (siw *ServerInterfaceWrapper) GetRepository(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error - // ------------- Body parse ------------- - var body CreateMergeRequestJSONRequestBody - parseBody := true - if parseBody { - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - http.Error(w, "Error unmarshalling body 'CreateMergeRequest' as JSON", http.StatusBadRequest) - return - } - } - // ------------- Path parameter "owner" ------------- var owner string @@ -7147,7 +8133,7 @@ func (siw *ServerInterfaceWrapper) CreateMergeRequest(w http.ResponseWriter, r * ctx = context.WithValue(ctx, TimestampScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CreateMergeRequest(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository) + siw.Handler.GetRepository(r.Context(), &JiaozifsResponse{w}, r, owner, repository) })) for _, middleware := range siw.HandlerMiddlewares { @@ -7157,18 +8143,18 @@ func (siw *ServerInterfaceWrapper) CreateMergeRequest(w http.ResponseWriter, r * handler.ServeHTTP(w, r.WithContext(ctx)) } -// Merge operation middleware -func (siw *ServerInterfaceWrapper) Merge(w http.ResponseWriter, r *http.Request) { +// UpdateRepository operation middleware +func (siw *ServerInterfaceWrapper) UpdateRepository(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error // ------------- Body parse ------------- - var body MergeJSONRequestBody + var body UpdateRepositoryJSONRequestBody parseBody := true if parseBody { if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - http.Error(w, "Error unmarshalling body 'Merge' as JSON", http.StatusBadRequest) + http.Error(w, "Error unmarshalling body 'UpdateRepository' as JSON", http.StatusBadRequest) return } } @@ -7191,15 +8177,6 @@ func (siw *ServerInterfaceWrapper) Merge(w http.ResponseWriter, r *http.Request) return } - // ------------- Path parameter "mrSeq" ------------- - var mrSeq uint64 - - err = runtime.BindStyledParameterWithOptions("simple", "mrSeq", chi.URLParam(r, "mrSeq"), &mrSeq, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "mrSeq", Err: err}) - return - } - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) ctx = context.WithValue(ctx, Basic_authScopes, []string{}) @@ -7217,7 +8194,7 @@ func (siw *ServerInterfaceWrapper) Merge(w http.ResponseWriter, r *http.Request) ctx = context.WithValue(ctx, TimestampScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.Merge(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository, mrSeq) + siw.Handler.UpdateRepository(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository) })) for _, middleware := range siw.HandlerMiddlewares { @@ -7227,8 +8204,8 @@ func (siw *ServerInterfaceWrapper) Merge(w http.ResponseWriter, r *http.Request) handler.ServeHTTP(w, r.WithContext(ctx)) } -// DeleteObject operation middleware -func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.Request) { +// DeleteBranch operation middleware +func (siw *ServerInterfaceWrapper) DeleteBranch(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -7268,7 +8245,7 @@ func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.R ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context - var params DeleteObjectParams + var params DeleteBranchParams // ------------- Required query parameter "refName" ------------- @@ -7285,23 +8262,8 @@ func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.R return } - // ------------- Required query parameter "path" ------------- - - if paramValue := r.URL.Query().Get("path"); paramValue != "" { - - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) - return - } - - err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) - return - } - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.DeleteObject(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + siw.Handler.DeleteBranch(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -7311,8 +8273,8 @@ func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.R handler.ServeHTTP(w, r.WithContext(ctx)) } -// GetObject operation middleware -func (siw *ServerInterfaceWrapper) GetObject(w http.ResponseWriter, r *http.Request) { +// GetBranch operation middleware +func (siw *ServerInterfaceWrapper) GetBranch(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -7352,22 +8314,7 @@ func (siw *ServerInterfaceWrapper) GetObject(w http.ResponseWriter, r *http.Requ ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context - var params GetObjectParams - - // ------------- Required query parameter "type" ------------- - - if paramValue := r.URL.Query().Get("type"); paramValue != "" { - - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "type"}) - return - } - - err = runtime.BindQueryParameter("form", true, true, "type", r.URL.Query(), ¶ms.Type) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "type", Err: err}) - return - } + var params GetBranchParams // ------------- Required query parameter "refName" ------------- @@ -7384,44 +8331,69 @@ func (siw *ServerInterfaceWrapper) GetObject(w http.ResponseWriter, r *http.Requ return } - // ------------- Required query parameter "path" ------------- + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetBranch(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + })) - if paramValue := r.URL.Query().Get("path"); paramValue != "" { + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// CreateBranch operation middleware +func (siw *ServerInterfaceWrapper) CreateBranch(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Body parse ------------- + var body CreateBranchJSONRequestBody + parseBody := true + if parseBody { + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "Error unmarshalling body 'CreateBranch' as JSON", http.StatusBadRequest) + return + } + } + + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) return } - err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) return } - headers := r.Header + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - // ------------- Optional header parameter "Range" ------------- - if valueList, found := headers[http.CanonicalHeaderKey("Range")]; found { - var Range string - n := len(valueList) - if n != 1 { - siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Range", Count: n}) - return - } + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) - err = runtime.BindStyledParameterWithOptions("simple", "Range", valueList[0], &Range, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Range", Err: err}) - return - } + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) - params.Range = &Range + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) - } + ctx = context.WithValue(ctx, TimestampScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetObject(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + siw.Handler.CreateBranch(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository) })) for _, middleware := range siw.HandlerMiddlewares { @@ -7431,8 +8403,8 @@ func (siw *ServerInterfaceWrapper) GetObject(w http.ResponseWriter, r *http.Requ handler.ServeHTTP(w, r.WithContext(ctx)) } -// HeadObject operation middleware -func (siw *ServerInterfaceWrapper) HeadObject(w http.ResponseWriter, r *http.Request) { +// ListBranches operation middleware +func (siw *ServerInterfaceWrapper) ListBranches(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -7472,76 +8444,105 @@ func (siw *ServerInterfaceWrapper) HeadObject(w http.ResponseWriter, r *http.Req ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context - var params HeadObjectParams - - // ------------- Required query parameter "type" ------------- + var params ListBranchesParams - if paramValue := r.URL.Query().Get("type"); paramValue != "" { + // ------------- Optional query parameter "prefix" ------------- - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "type"}) + err = runtime.BindQueryParameter("form", true, false, "prefix", r.URL.Query(), ¶ms.Prefix) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "prefix", Err: err}) return } - err = runtime.BindQueryParameter("form", true, true, "type", r.URL.Query(), ¶ms.Type) + // ------------- Optional query parameter "after" ------------- + + err = runtime.BindQueryParameter("form", true, false, "after", r.URL.Query(), ¶ms.After) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "type", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "after", Err: err}) return } - // ------------- Required query parameter "refName" ------------- - - if paramValue := r.URL.Query().Get("refName"); paramValue != "" { + // ------------- Optional query parameter "amount" ------------- - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) + err = runtime.BindQueryParameter("form", true, false, "amount", r.URL.Query(), ¶ms.Amount) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "amount", Err: err}) return } - err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListBranches(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// GetCommitChanges operation middleware +func (siw *ServerInterfaceWrapper) GetCommitChanges(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) return } - // ------------- Required query parameter "path" ------------- - - if paramValue := r.URL.Query().Get("path"); paramValue != "" { + // ------------- Path parameter "repository" ------------- + var repository string - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) return } - err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) + // ------------- Path parameter "commit_id" ------------- + var commitId string + + err = runtime.BindStyledParameterWithOptions("simple", "commit_id", chi.URLParam(r, "commit_id"), &commitId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "commit_id", Err: err}) return } - headers := r.Header + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - // ------------- Optional header parameter "Range" ------------- - if valueList, found := headers[http.CanonicalHeaderKey("Range")]; found { - var Range string - n := len(valueList) - if n != 1 { - siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Range", Count: n}) - return - } + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) - err = runtime.BindStyledParameterWithOptions("simple", "Range", valueList[0], &Range, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Range", Err: err}) - return - } + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) - params.Range = &Range + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetCommitChangesParams + + // ------------- Optional query parameter "path" ------------- + + err = runtime.BindQueryParameter("form", true, false, "path", r.URL.Query(), ¶ms.Path) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.HeadObject(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + siw.Handler.GetCommitChanges(r.Context(), &JiaozifsResponse{w}, r, owner, repository, commitId, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -7551,8 +8552,8 @@ func (siw *ServerInterfaceWrapper) HeadObject(w http.ResponseWriter, r *http.Req handler.ServeHTTP(w, r.WithContext(ctx)) } -// UploadObject operation middleware -func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.Request) { +// GetCommitsInRef operation middleware +func (siw *ServerInterfaceWrapper) GetCommitsInRef(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -7592,40 +8593,34 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context - var params UploadObjectParams - - // ------------- Required query parameter "refName" ------------- - - if paramValue := r.URL.Query().Get("refName"); paramValue != "" { + var params GetCommitsInRefParams - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) - return - } + // ------------- Optional query parameter "after" ------------- - err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) + err = runtime.BindQueryParameter("form", true, false, "after", r.URL.Query(), ¶ms.After) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "after", Err: err}) return } - // ------------- Required query parameter "path" ------------- - - if paramValue := r.URL.Query().Get("path"); paramValue != "" { + // ------------- Optional query parameter "amount" ------------- - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) + err = runtime.BindQueryParameter("form", true, false, "amount", r.URL.Query(), ¶ms.Amount) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "amount", Err: err}) return } - err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) + // ------------- Optional query parameter "refName" ------------- + + err = runtime.BindQueryParameter("form", true, false, "refName", r.URL.Query(), ¶ms.RefName) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) return } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.UploadObject(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + siw.Handler.GetCommitsInRef(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -7635,8 +8630,8 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R handler.ServeHTTP(w, r.WithContext(ctx)) } -// DeleteRepository operation middleware -func (siw *ServerInterfaceWrapper) DeleteRepository(w http.ResponseWriter, r *http.Request) { +// CompareCommit operation middleware +func (siw *ServerInterfaceWrapper) CompareCommit(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -7659,6 +8654,15 @@ func (siw *ServerInterfaceWrapper) DeleteRepository(w http.ResponseWriter, r *ht return } + // ------------- Path parameter "basehead" ------------- + var basehead string + + err = runtime.BindStyledParameterWithOptions("simple", "basehead", chi.URLParam(r, "basehead"), &basehead, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "basehead", Err: err}) + return + } + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) ctx = context.WithValue(ctx, Basic_authScopes, []string{}) @@ -7676,18 +8680,18 @@ func (siw *ServerInterfaceWrapper) DeleteRepository(w http.ResponseWriter, r *ht ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context - var params DeleteRepositoryParams + var params CompareCommitParams - // ------------- Optional query parameter "is_clean_data" ------------- + // ------------- Optional query parameter "path" ------------- - err = runtime.BindQueryParameter("form", true, false, "is_clean_data", r.URL.Query(), ¶ms.IsCleanData) + err = runtime.BindQueryParameter("form", true, false, "path", r.URL.Query(), ¶ms.Path) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "is_clean_data", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) return } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.DeleteRepository(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + siw.Handler.CompareCommit(r.Context(), &JiaozifsResponse{w}, r, owner, repository, basehead, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -7697,8 +8701,8 @@ func (siw *ServerInterfaceWrapper) DeleteRepository(w http.ResponseWriter, r *ht handler.ServeHTTP(w, r.WithContext(ctx)) } -// GetRepository operation middleware -func (siw *ServerInterfaceWrapper) GetRepository(w http.ResponseWriter, r *http.Request) { +// GetEntriesInRef operation middleware +func (siw *ServerInterfaceWrapper) GetEntriesInRef(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -7737,8 +8741,42 @@ func (siw *ServerInterfaceWrapper) GetRepository(w http.ResponseWriter, r *http. ctx = context.WithValue(ctx, TimestampScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context + var params GetEntriesInRefParams + + // ------------- Optional query parameter "path" ------------- + + err = runtime.BindQueryParameter("form", true, false, "path", r.URL.Query(), ¶ms.Path) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + // ------------- Optional query parameter "ref" ------------- + + err = runtime.BindQueryParameter("form", true, false, "ref", r.URL.Query(), ¶ms.Ref) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "ref", Err: err}) + return + } + + // ------------- Required query parameter "type" ------------- + + if paramValue := r.URL.Query().Get("type"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "type"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "type", r.URL.Query(), ¶ms.Type) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "type", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetRepository(r.Context(), &JiaozifsResponse{w}, r, owner, repository) + siw.Handler.GetEntriesInRef(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -7748,22 +8786,12 @@ func (siw *ServerInterfaceWrapper) GetRepository(w http.ResponseWriter, r *http. handler.ServeHTTP(w, r.WithContext(ctx)) } -// UpdateRepository operation middleware -func (siw *ServerInterfaceWrapper) UpdateRepository(w http.ResponseWriter, r *http.Request) { +// RevokeMember operation middleware +func (siw *ServerInterfaceWrapper) RevokeMember(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error - // ------------- Body parse ------------- - var body UpdateRepositoryJSONRequestBody - parseBody := true - if parseBody { - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - http.Error(w, "Error unmarshalling body 'UpdateRepository' as JSON", http.StatusBadRequest) - return - } - } - // ------------- Path parameter "owner" ------------- var owner string @@ -7798,8 +8826,26 @@ func (siw *ServerInterfaceWrapper) UpdateRepository(w http.ResponseWriter, r *ht ctx = context.WithValue(ctx, TimestampScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context + var params RevokeMemberParams + + // ------------- Required query parameter "user_id" ------------- + + if paramValue := r.URL.Query().Get("user_id"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "user_id"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "user_id", r.URL.Query(), ¶ms.UserId) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user_id", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.UpdateRepository(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository) + siw.Handler.RevokeMember(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -7809,8 +8855,8 @@ func (siw *ServerInterfaceWrapper) UpdateRepository(w http.ResponseWriter, r *ht handler.ServeHTTP(w, r.WithContext(ctx)) } -// DeleteBranch operation middleware -func (siw *ServerInterfaceWrapper) DeleteBranch(w http.ResponseWriter, r *http.Request) { +// UpdateMemberGroup operation middleware +func (siw *ServerInterfaceWrapper) UpdateMemberGroup(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -7850,25 +8896,40 @@ func (siw *ServerInterfaceWrapper) DeleteBranch(w http.ResponseWriter, r *http.R ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context - var params DeleteBranchParams + var params UpdateMemberGroupParams - // ------------- Required query parameter "refName" ------------- + // ------------- Required query parameter "user_id" ------------- - if paramValue := r.URL.Query().Get("refName"); paramValue != "" { + if paramValue := r.URL.Query().Get("user_id"); paramValue != "" { } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "user_id"}) return } - err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) + err = runtime.BindQueryParameter("form", true, true, "user_id", r.URL.Query(), ¶ms.UserId) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user_id", Err: err}) + return + } + + // ------------- Required query parameter "group_id" ------------- + + if paramValue := r.URL.Query().Get("group_id"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "group_id"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "group_id", r.URL.Query(), ¶ms.GroupId) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "group_id", Err: err}) return } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.DeleteBranch(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + siw.Handler.UpdateMemberGroup(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -7878,8 +8939,8 @@ func (siw *ServerInterfaceWrapper) DeleteBranch(w http.ResponseWriter, r *http.R handler.ServeHTTP(w, r.WithContext(ctx)) } -// GetBranch operation middleware -func (siw *ServerInterfaceWrapper) GetBranch(w http.ResponseWriter, r *http.Request) { +// InviteMember operation middleware +func (siw *ServerInterfaceWrapper) InviteMember(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -7919,25 +8980,40 @@ func (siw *ServerInterfaceWrapper) GetBranch(w http.ResponseWriter, r *http.Requ ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context - var params GetBranchParams + var params InviteMemberParams - // ------------- Required query parameter "refName" ------------- + // ------------- Required query parameter "user_id" ------------- - if paramValue := r.URL.Query().Get("refName"); paramValue != "" { + if paramValue := r.URL.Query().Get("user_id"); paramValue != "" { } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "user_id"}) return } - err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) + err = runtime.BindQueryParameter("form", true, true, "user_id", r.URL.Query(), ¶ms.UserId) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "user_id", Err: err}) + return + } + + // ------------- Required query parameter "group_id" ------------- + + if paramValue := r.URL.Query().Get("group_id"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "group_id"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "group_id", r.URL.Query(), ¶ms.GroupId) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "group_id", Err: err}) return } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetBranch(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + siw.Handler.InviteMember(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -7947,22 +9023,12 @@ func (siw *ServerInterfaceWrapper) GetBranch(w http.ResponseWriter, r *http.Requ handler.ServeHTTP(w, r.WithContext(ctx)) } -// CreateBranch operation middleware -func (siw *ServerInterfaceWrapper) CreateBranch(w http.ResponseWriter, r *http.Request) { +// ListMembers operation middleware +func (siw *ServerInterfaceWrapper) ListMembers(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error - // ------------- Body parse ------------- - var body CreateBranchJSONRequestBody - parseBody := true - if parseBody { - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - http.Error(w, "Error unmarshalling body 'CreateBranch' as JSON", http.StatusBadRequest) - return - } - } - // ------------- Path parameter "owner" ------------- var owner string @@ -7998,7 +9064,7 @@ func (siw *ServerInterfaceWrapper) CreateBranch(w http.ResponseWriter, r *http.R ctx = context.WithValue(ctx, TimestampScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CreateBranch(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository) + siw.Handler.ListMembers(r.Context(), &JiaozifsResponse{w}, r, owner, repository) })) for _, middleware := range siw.HandlerMiddlewares { @@ -8008,8 +9074,8 @@ func (siw *ServerInterfaceWrapper) CreateBranch(w http.ResponseWriter, r *http.R handler.ServeHTTP(w, r.WithContext(ctx)) } -// ListBranches operation middleware -func (siw *ServerInterfaceWrapper) ListBranches(w http.ResponseWriter, r *http.Request) { +// ListMergeRequests operation middleware +func (siw *ServerInterfaceWrapper) ListMergeRequests(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -8049,15 +9115,7 @@ func (siw *ServerInterfaceWrapper) ListBranches(w http.ResponseWriter, r *http.R ctx = context.WithValue(ctx, TimestampScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context - var params ListBranchesParams - - // ------------- Optional query parameter "prefix" ------------- - - err = runtime.BindQueryParameter("form", true, false, "prefix", r.URL.Query(), ¶ms.Prefix) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "prefix", Err: err}) - return - } + var params ListMergeRequestsParams // ------------- Optional query parameter "after" ------------- @@ -8075,8 +9133,16 @@ func (siw *ServerInterfaceWrapper) ListBranches(w http.ResponseWriter, r *http.R return } + // ------------- Optional query parameter "state" ------------- + + err = runtime.BindQueryParameter("form", true, false, "state", r.URL.Query(), ¶ms.State) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "state", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.ListBranches(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + siw.Handler.ListMergeRequests(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -8086,12 +9152,22 @@ func (siw *ServerInterfaceWrapper) ListBranches(w http.ResponseWriter, r *http.R handler.ServeHTTP(w, r.WithContext(ctx)) } -// GetCommitChanges operation middleware -func (siw *ServerInterfaceWrapper) GetCommitChanges(w http.ResponseWriter, r *http.Request) { +// CreateMergeRequest operation middleware +func (siw *ServerInterfaceWrapper) CreateMergeRequest(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error + // ------------- Body parse ------------- + var body CreateMergeRequestJSONRequestBody + parseBody := true + if parseBody { + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "Error unmarshalling body 'CreateMergeRequest' as JSON", http.StatusBadRequest) + return + } + } + // ------------- Path parameter "owner" ------------- var owner string @@ -8110,15 +9186,6 @@ func (siw *ServerInterfaceWrapper) GetCommitChanges(w http.ResponseWriter, r *ht return } - // ------------- Path parameter "commit_id" ------------- - var commitId string - - err = runtime.BindStyledParameterWithOptions("simple", "commit_id", chi.URLParam(r, "commit_id"), &commitId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "commit_id", Err: err}) - return - } - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) ctx = context.WithValue(ctx, Basic_authScopes, []string{}) @@ -8135,19 +9202,8 @@ func (siw *ServerInterfaceWrapper) GetCommitChanges(w http.ResponseWriter, r *ht ctx = context.WithValue(ctx, TimestampScopes, []string{}) - // Parameter object where we will unmarshal all parameters from the context - var params GetCommitChangesParams - - // ------------- Optional query parameter "path" ------------- - - err = runtime.BindQueryParameter("form", true, false, "path", r.URL.Query(), ¶ms.Path) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) - return - } - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetCommitChanges(r.Context(), &JiaozifsResponse{w}, r, owner, repository, commitId, params) + siw.Handler.CreateMergeRequest(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository) })) for _, middleware := range siw.HandlerMiddlewares { @@ -8157,8 +9213,8 @@ func (siw *ServerInterfaceWrapper) GetCommitChanges(w http.ResponseWriter, r *ht handler.ServeHTTP(w, r.WithContext(ctx)) } -// GetCommitsInRef operation middleware -func (siw *ServerInterfaceWrapper) GetCommitsInRef(w http.ResponseWriter, r *http.Request) { +// GetMergeRequest operation middleware +func (siw *ServerInterfaceWrapper) GetMergeRequest(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error @@ -8181,6 +9237,15 @@ func (siw *ServerInterfaceWrapper) GetCommitsInRef(w http.ResponseWriter, r *htt return } + // ------------- Path parameter "mrSeq" ------------- + var mrSeq uint64 + + err = runtime.BindStyledParameterWithOptions("simple", "mrSeq", chi.URLParam(r, "mrSeq"), &mrSeq, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "mrSeq", Err: err}) + return + } + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) ctx = context.WithValue(ctx, Basic_authScopes, []string{}) @@ -8197,35 +9262,8 @@ func (siw *ServerInterfaceWrapper) GetCommitsInRef(w http.ResponseWriter, r *htt ctx = context.WithValue(ctx, TimestampScopes, []string{}) - // Parameter object where we will unmarshal all parameters from the context - var params GetCommitsInRefParams - - // ------------- Optional query parameter "after" ------------- - - err = runtime.BindQueryParameter("form", true, false, "after", r.URL.Query(), ¶ms.After) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "after", Err: err}) - return - } - - // ------------- Optional query parameter "amount" ------------- - - err = runtime.BindQueryParameter("form", true, false, "amount", r.URL.Query(), ¶ms.Amount) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "amount", Err: err}) - return - } - - // ------------- Optional query parameter "refName" ------------- - - err = runtime.BindQueryParameter("form", true, false, "refName", r.URL.Query(), ¶ms.RefName) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) - return - } - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetCommitsInRef(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + siw.Handler.GetMergeRequest(r.Context(), &JiaozifsResponse{w}, r, owner, repository, mrSeq) })) for _, middleware := range siw.HandlerMiddlewares { @@ -8235,12 +9273,22 @@ func (siw *ServerInterfaceWrapper) GetCommitsInRef(w http.ResponseWriter, r *htt handler.ServeHTTP(w, r.WithContext(ctx)) } -// CompareCommit operation middleware -func (siw *ServerInterfaceWrapper) CompareCommit(w http.ResponseWriter, r *http.Request) { +// UpdateMergeRequest operation middleware +func (siw *ServerInterfaceWrapper) UpdateMergeRequest(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error + // ------------- Body parse ------------- + var body UpdateMergeRequestJSONRequestBody + parseBody := true + if parseBody { + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "Error unmarshalling body 'UpdateMergeRequest' as JSON", http.StatusBadRequest) + return + } + } + // ------------- Path parameter "owner" ------------- var owner string @@ -8259,12 +9307,12 @@ func (siw *ServerInterfaceWrapper) CompareCommit(w http.ResponseWriter, r *http. return } - // ------------- Path parameter "basehead" ------------- - var basehead string + // ------------- Path parameter "mrSeq" ------------- + var mrSeq uint64 - err = runtime.BindStyledParameterWithOptions("simple", "basehead", chi.URLParam(r, "basehead"), &basehead, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "mrSeq", chi.URLParam(r, "mrSeq"), &mrSeq, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "basehead", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "mrSeq", Err: err}) return } @@ -8284,19 +9332,8 @@ func (siw *ServerInterfaceWrapper) CompareCommit(w http.ResponseWriter, r *http. ctx = context.WithValue(ctx, TimestampScopes, []string{}) - // Parameter object where we will unmarshal all parameters from the context - var params CompareCommitParams - - // ------------- Optional query parameter "path" ------------- - - err = runtime.BindQueryParameter("form", true, false, "path", r.URL.Query(), ¶ms.Path) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) - return - } - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CompareCommit(r.Context(), &JiaozifsResponse{w}, r, owner, repository, basehead, params) + siw.Handler.UpdateMergeRequest(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository, mrSeq) })) for _, middleware := range siw.HandlerMiddlewares { @@ -8306,12 +9343,22 @@ func (siw *ServerInterfaceWrapper) CompareCommit(w http.ResponseWriter, r *http. handler.ServeHTTP(w, r.WithContext(ctx)) } -// GetEntriesInRef operation middleware -func (siw *ServerInterfaceWrapper) GetEntriesInRef(w http.ResponseWriter, r *http.Request) { +// Merge operation middleware +func (siw *ServerInterfaceWrapper) Merge(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error + // ------------- Body parse ------------- + var body MergeJSONRequestBody + parseBody := true + if parseBody { + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "Error unmarshalling body 'Merge' as JSON", http.StatusBadRequest) + return + } + } + // ------------- Path parameter "owner" ------------- var owner string @@ -8330,6 +9377,15 @@ func (siw *ServerInterfaceWrapper) GetEntriesInRef(w http.ResponseWriter, r *htt return } + // ------------- Path parameter "mrSeq" ------------- + var mrSeq uint64 + + err = runtime.BindStyledParameterWithOptions("simple", "mrSeq", chi.URLParam(r, "mrSeq"), &mrSeq, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "mrSeq", Err: err}) + return + } + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) ctx = context.WithValue(ctx, Basic_authScopes, []string{}) @@ -8346,42 +9402,8 @@ func (siw *ServerInterfaceWrapper) GetEntriesInRef(w http.ResponseWriter, r *htt ctx = context.WithValue(ctx, TimestampScopes, []string{}) - // Parameter object where we will unmarshal all parameters from the context - var params GetEntriesInRefParams - - // ------------- Optional query parameter "path" ------------- - - err = runtime.BindQueryParameter("form", true, false, "path", r.URL.Query(), ¶ms.Path) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) - return - } - - // ------------- Optional query parameter "ref" ------------- - - err = runtime.BindQueryParameter("form", true, false, "ref", r.URL.Query(), ¶ms.Ref) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "ref", Err: err}) - return - } - - // ------------- Required query parameter "type" ------------- - - if paramValue := r.URL.Query().Get("type"); paramValue != "" { - - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "type"}) - return - } - - err = runtime.BindQueryParameter("form", true, true, "type", r.URL.Query(), ¶ms.Type) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "type", Err: err}) - return - } - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetEntriesInRef(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + siw.Handler.Merge(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository, mrSeq) })) for _, middleware := range siw.HandlerMiddlewares { @@ -9497,19 +10519,7 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Post(options.BaseURL+"/auth/logout", wrapper.Logout) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/mergequest/{owner}/{repository}/{mrSeq}", wrapper.GetMergeRequest) - }) - r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/mergequest/{owner}/{repository}/{mrSeq}", wrapper.UpdateMergeRequest) - }) - r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/mergerequest/{owner}/{repository}", wrapper.ListMergeRequests) - }) - r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/mergerequest/{owner}/{repository}", wrapper.CreateMergeRequest) - }) - r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/mergerequest/{owner}/{repository}/{mrSeq}/merge", wrapper.Merge) + r.Get(options.BaseURL+"/groups/repo", wrapper.ListRepoGroup) }) r.Group(func(r chi.Router) { r.Delete(options.BaseURL+"/object/{owner}/{repository}", wrapper.DeleteObject) @@ -9556,6 +10566,33 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/repos/{owner}/{repository}/contents", wrapper.GetEntriesInRef) }) + r.Group(func(r chi.Router) { + r.Delete(options.BaseURL+"/repos/{owner}/{repository}/member", wrapper.RevokeMember) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/repos/{owner}/{repository}/member", wrapper.UpdateMemberGroup) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/repos/{owner}/{repository}/member/invite", wrapper.InviteMember) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/repos/{owner}/{repository}/members", wrapper.ListMembers) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/repos/{owner}/{repository}/mergerequest", wrapper.ListMergeRequests) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/repos/{owner}/{repository}/mergerequest", wrapper.CreateMergeRequest) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/repos/{owner}/{repository}/mergerequest/{mrSeq}", wrapper.GetMergeRequest) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/repos/{owner}/{repository}/mergerequest/{mrSeq}", wrapper.UpdateMergeRequest) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/repos/{owner}/{repository}/mergerequest/{mrSeq}/merge", wrapper.Merge) + }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/setup", wrapper.GetSetupState) }) @@ -9620,92 +10657,96 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "///6wwPHsJQAAA==", + "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", + "2hq8tsPi1Yvb/wsAAP//tmwzG1CjAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 225a8506..619c2fc4 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -144,6 +144,59 @@ components: description: true if the comm prefs are missing. login_config: $ref: "#/components/schemas/LoginConfig" + Group: + type: object + required: + - id + - name + - policies + - created_at + - updated_at + properties: + id: + type: string + format: uuid + name: + type: string + policies: + type: array + items: + type: string + format: uuid + created_at: + type: integer + format: int64 + updated_at: + type: integer + format: int64 + Member: + type: object + required: + - id + - user_id + - group_id + - repo_id + - created_at + - updated_at + properties: + id: + type: string + format: uuid + user_id: + type: string + format: uuid + repo_id: + type: string + format: uuid + group_id: + type: string + format: uuid + created_at: + type: integer + format: int64 + updated_at: + type: integer + format: int64 AkskList: type: object required: @@ -1702,7 +1755,7 @@ paths: 403: description: Forbidden - /mergerequest/{owner}/{repository}: + /repos/{owner}/{repository}/mergerequest: parameters: - in: path name: owner @@ -1770,7 +1823,7 @@ paths: 500: description: Internal Server Error - /mergerequest/{owner}/{repository}/{mrSeq}/merge: + /repos/{owner}/{repository}/mergerequest/{mrSeq}/merge: parameters: - in: path name: owner @@ -1816,7 +1869,8 @@ paths: description: Too many requests 500: description: Internal Server Error - /mergequest/{owner}/{repository}/{mrSeq}: + + /repos/{owner}/{repository}/mergerequest/{mrSeq}: parameters: - in: path name: owner @@ -1877,6 +1931,147 @@ paths: 500: description: Internal Server Error + /repos/{owner}/{repository}/members: + parameters: + - in: path + name: owner + required: true + schema: + type: string + - in: path + name: repository + required: true + schema: + type: string + get: + tags: + - listMembers + operationId: listMembers + summary: get list of members in repository + responses: + 200: + description: array of member + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Member" + 401: + description: Unauthorized + 404: + description: Resource Not Found + 420: + description: Too many requests + 500: + description: Internal Server Error + + /repos/{owner}/{repository}/member: + parameters: + - in: path + name: owner + required: true + schema: + type: string + - in: path + name: repository + required: true + schema: + type: string + post: + tags: + - member + operationId: updateMemberGroup + summary: update member by user id and change group role + parameters: + - in: query + name: user_id + required: true + schema: + type: string + format: uuid + - in: query + name: group_id + required: true + schema: + type: string + format: uuid + responses: + 200: + description: Update member group success + 401: + description: Unauthorized + 404: + description: Resource Not Found + 420: + description: Too many requests + 500: + description: Internal Server Error + delete: + tags: + - member + operationId: revokeMember + summary: Revoke member in repository + parameters: + - in: query + name: user_id + required: true + schema: + type: string + format: uuid + responses: + 200: + description: revoke member success + 401: + description: Unauthorized + 404: + description: Resource Not Found + 420: + description: Too many requests + 500: + description: Internal Server Error + /repos/{owner}/{repository}/member/invite: + parameters: + - in: path + name: owner + required: true + schema: + type: string + - in: path + name: repository + required: true + schema: + type: string + post: + tags: + - member + operationId: inviteMember + summary: invite member + parameters: + - in: query + name: user_id + required: true + schema: + type: string + format: uuid + - in: query + name: group_id + required: true + schema: + type: string + format: uuid + responses: + 200: + description: Invite member success + 401: + description: Unauthorized + 404: + description: Resource Not Found + 420: + description: Too many requests + 500: + description: Internal Server Error + /users/{owner}/repos: parameters: - in: path @@ -1907,6 +2102,27 @@ paths: 403: description: Forbidden + /groups/repo: + get: + tags: + - group + operationId: listRepoGroup + summary: list groups for repo + responses: + 200: + description: list repo's group + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Group" + 400: + description: ValidationError + 401: + description: Unauthorized + 403: + description: Forbidden /users/repos: # 必须授权 get: tags: @@ -2154,6 +2370,10 @@ paths: responses: 201: description: registration success + content: + application/json: + schema: + $ref: "#/components/schemas/UserInfo" 400: description: Bad Request - Validation Error 420: diff --git a/auth/auth_middleware.go b/auth/auth_middleware.go index 6d9dd724..7550b451 100644 --- a/auth/auth_middleware.go +++ b/auth/auth_middleware.go @@ -7,6 +7,8 @@ import ( "net/http" "strings" + logging "github.com/ipfs/go-log/v2" + "github.com/jiaozifs/jiaozifs/utils" "github.com/jiaozifs/jiaozifs/auth/aksk" @@ -27,6 +29,7 @@ const ( IDTokenClaimsSessionKey = "id_token_claims" ) +var log = logging.Logger("auth") var ( ErrFailedToAccessStorage = errors.New("failed to access storage") ErrAuthenticatingRequest = errors.New("error authenticating request") diff --git a/auth/basic_auth.go b/auth/basic_auth.go deleted file mode 100644 index 4d6dc45f..00000000 --- a/auth/basic_auth.go +++ /dev/null @@ -1,61 +0,0 @@ -package auth - -import ( - "context" - "fmt" - "time" - - logging "github.com/ipfs/go-log/v2" - "github.com/jiaozifs/jiaozifs/models" - "golang.org/x/crypto/bcrypt" -) - -var log = logging.Logger("auth") - -type Register struct { - Username string `json:"username"` - Email string `json:"email"` - Password string `json:"password"` -} - -func (r *Register) Register(ctx context.Context, repo models.IUserRepo) error { - // check username, email - count1, err := repo.Count(ctx, models.NewCountUserParam().SetName(r.Username)) - if err != nil { - return err - } - count2, err := repo.Count(ctx, models.NewCountUserParam().SetName(r.Email)) - if err != nil { - return err - } - - if count1+count2 > 0 { - return fmt.Errorf("username %s or email %s not found %w ", r.Username, r.Email, ErrInvalidNameEmail) - } - - // reserve temporarily - password, err := bcrypt.GenerateFromPassword([]byte(r.Password), passwordCost) - if err != nil { - return fmt.Errorf("invalid password %w", err) - } - - // insert db - user := &models.User{ - Name: r.Username, - Email: r.Email, - EncryptedPassword: string(password), - CurrentSignInAt: time.Time{}, - LastSignInAt: time.Time{}, - CurrentSignInIP: "", - LastSignInIP: "", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - insertUser, err := repo.Insert(ctx, user) - if err != nil { - return fmt.Errorf("inser user %s user error %w", r.Username, err) - } - - log.Infof("%s registration success", insertUser.Name) - return nil -} diff --git a/auth/rbac/arn.go b/auth/rbac/arn.go new file mode 100644 index 00000000..18519af5 --- /dev/null +++ b/auth/rbac/arn.go @@ -0,0 +1,119 @@ +package rbac + +import ( + "errors" + "strings" + + "github.com/jiaozifs/jiaozifs/auth/rbac/wildcard" + "github.com/jiaozifs/jiaozifs/models/rbacmodel" +) + +var ( + ErrInvalidArn = errors.New("invalid ARN") +) + +type Arn struct { + Partition string + Service string + Region string + AccountID string + ResourceID string +} + +const ( + fieldIndexArn = iota + fieldIndexPartition + fieldIndexService + fieldIndexRegion + fieldIndexAccount + fieldIndexResource + + numFieldIndexes +) + +func arnParseField(arn *Arn, field string, fieldIndex int) error { + switch fieldIndex { + case fieldIndexArn: + if field != "arn" { + return ErrInvalidArn + } + case fieldIndexPartition: + if field != "gitdata" { + return ErrInvalidArn + } + arn.Partition = field + case fieldIndexService: + if len(field) < 1 { + return ErrInvalidArn + } + if field != "jiaozifs" { + return ErrInvalidArn + } + arn.Service = field + case fieldIndexRegion: + arn.Region = field + case fieldIndexAccount: + arn.AccountID = field + case fieldIndexResource: + if len(field) < 1 { + return ErrInvalidArn + } + arn.ResourceID = field + } + return nil +} + +func ParseARN(arnString string) (*Arn, error) { + // BUG(ozkatz): This parser does not handle resource types. Handling resource types is + // subtle: they may be separated from resource IDs by a colon OR by a slash. For an + // example of a resource type, see ECS[1] (uses only slash separators). That colons + // are an acceptable separator appears in [2], so a workaround to this limitation is + // to use a slash. + // + // [1] https://docs.aws.amazon.com/AmazonECS/latest/developerguide/security_iam_service-with-iam.html#security_iam_service-with-iam-id-based-policies-resources + // [2] https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#arns-syntax + parts := strings.SplitN(arnString, ":", numFieldIndexes) + + if len(parts) < numFieldIndexes { + return nil, ErrInvalidArn + } + + arn := &Arn{} + + for currField, part := range parts { + err := arnParseField(arn, part, currField) + if err != nil { + return arn, err + } + } + + return arn, nil +} + +func ArnMatch(src, dst string) bool { + if src == string(rbacmodel.All) { + return true + } + source, err := ParseARN(src) + if err != nil { + return false + } + dest, err := ParseARN(dst) + if err != nil { + return false + } + if source.Service != dest.Service { + return false + } + if source.Partition != dest.Partition { + return false + } + if source.AccountID != dest.AccountID { + return false + } + // wildcards are allowed for resources only + if wildcard.Match(source.ResourceID, dest.ResourceID) { + return true + } + return false +} diff --git a/auth/rbac/arn_test.go b/auth/rbac/arn_test.go new file mode 100644 index 00000000..a2f0fc07 --- /dev/null +++ b/auth/rbac/arn_test.go @@ -0,0 +1,99 @@ +package rbac_test + +import ( + "testing" + + "github.com/jiaozifs/jiaozifs/auth/rbac" +) + +func TestParseARN(t *testing.T) { + cases := []struct { + Input string + Arn rbac.Arn + Error bool + }{ + {Input: "", Error: true}, + {Input: "arn:gitdata:jiaozifs", Error: true}, + {Input: "arn:gitdata:jiaozifs:a:b:myrepo", Arn: rbac.Arn{ + Partition: "gitdata", + Service: "jiaozifs", + Region: "a", + AccountID: "b", + ResourceID: "myrepo"}}, + {Input: "arn:gitdata:jiaozifs:a::myrepo", Arn: rbac.Arn{ + Partition: "gitdata", + Service: "jiaozifs", + Region: "a", + AccountID: "", + ResourceID: "myrepo"}}, + {Input: "arn:gitdata:jiaozifs::b:myrepo", Arn: rbac.Arn{ + Partition: "gitdata", + Service: "jiaozifs", + Region: "", + AccountID: "b", + ResourceID: "myrepo"}}, + {Input: "arn:gitdata:jiaozifs:::myrepo", Arn: rbac.Arn{ + Partition: "gitdata", + Service: "jiaozifs", + Region: "", + AccountID: "", + ResourceID: "myrepo"}}, + {Input: "arn:gitdata:jiaozifs:::myrepo/branch/file:with:colon", Arn: rbac.Arn{ + Partition: "gitdata", + Service: "jiaozifs", + Region: "", + AccountID: "", + ResourceID: "myrepo/branch/file:with:colon"}}, + } + + for _, c := range cases { + got, err := rbac.ParseARN(c.Input) + if err != nil && !c.Error { + t.Fatalf("got unexpected error parsing arn: \"%s\": \"%s\"", c.Input, err) + } else if err != nil { + continue + } else if c.Error { + t.Fatalf("expected error parsing arn: \"%s\"", c.Input) + } + if got.AccountID != c.Arn.AccountID { + t.Fatalf("got unexpected account ID parsing arn: \"%s\": \"%s\" (expected \"%s\")", c.Input, got.AccountID, c.Arn.AccountID) + } + if got.Region != c.Arn.Region { + t.Fatalf("got unexpected region parsing arn: \"%s\": \"%s\" (expected \"%s\")", c.Input, got.Region, c.Arn.Region) + } + if got.Partition != c.Arn.Partition { + t.Fatalf("got unexpected partition parsing arn: \"%s\": \"%s\" (expected \"%s\")", c.Input, got.Partition, c.Arn.Partition) + } + if got.Service != c.Arn.Service { + t.Fatalf("got unexpected service parsing arn: \"%s\": \"%s\" (expected \"%s\")", c.Input, got.Service, c.Arn.Service) + } + if got.ResourceID != c.Arn.ResourceID { + t.Fatalf("got unexpected resource ID parsing arn: \"%s\": \"%s\" (expected \"%s\")", c.Input, got.ResourceID, c.Arn.ResourceID) + } + } +} + +func TestArnMatch(t *testing.T) { + cases := []struct { + InputSource string + InputDestination string + Match bool + }{ + {"arn:gitdata:jiaozifs::b:myrepo", "arn:gitdata:jiaozifs::b:myrepo", true}, + {"arn:gitdata:jiaozifs::b:*", "arn:gitdata:jiaozifs::b:myrepo", true}, + {"arn:gitdata:jiaozifs::b:my*", "arn:gitdata:jiaozifs::b:myrepo", true}, + {"arn:gitdata:jiaozifs::b:my*po", "arn:gitdata:jiaozifs::b:myrepo", true}, + {"arn:gitdata:jiaozifs::b:our*", "arn:gitdata:jiaozifs::b:myrepo", false}, + {"arn:gitdata:jiaozifs::b:my*own", "arn:gitdata:jiaozifs::b:myrepo", false}, + {"arn:gitdata:jiaozifs::b:myrepo", "arn:gitdata:jiaozifs::b:*", false}, + {"arn:gitdata:jiaozifs:::*", "arn:gitdata:jiaozifs:::*", true}, + {"arn:gitdata:repo", "arn:gitdata:repo", false}, + } + + for _, c := range cases { + got := rbac.ArnMatch(c.InputSource, c.InputDestination) + if got != c.Match { + t.Fatalf("expected match %v, got %v on source = %s, destination = %s", c.Match, got, c.InputSource, c.InputDestination) + } + } +} diff --git a/auth/rbac/rbac.go b/auth/rbac/rbac.go new file mode 100644 index 00000000..cf173a1f --- /dev/null +++ b/auth/rbac/rbac.go @@ -0,0 +1,359 @@ +package rbac + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/google/uuid" + + "github.com/jiaozifs/jiaozifs/auth/rbac/wildcard" + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/models/rbacmodel" +) + +var ErrInsufficientPermissions = fmt.Errorf("permission not enough") + +// CheckResult - the final result for the authorization is accepted only if it's CheckAllow +type CheckResult int + +type BuiltinGroupName = string + +const ( + // Super do anything in system + Super BuiltinGroupName = "Super" + // RepoAdmin do anything in this repo + RepoAdmin BuiltinGroupName = "RepoAdmin" + // RepoWrite read and write in this repo + RepoWrite BuiltinGroupName = "RepoWrite" + // RepoRead only read in this repo + RepoRead BuiltinGroupName = "RepoRead" + // UserOwnAccess could manage user, credential, create repo + UserOwnAccess BuiltinGroupName = "UserOwnAccess" +) +const ( + InvalidUserID = "" + maxPage = 1000 + // CheckAllow Permission allowed + CheckAllow CheckResult = iota + // CheckNeutral Permission neither allowed nor denied + CheckNeutral + // CheckDeny Permission denied + CheckDeny +) + +type Permission struct { + Action string + Resource rbacmodel.Resource +} + +type NodeType int + +const ( + NodeTypeNode NodeType = iota + NodeTypeOr + NodeTypeAnd +) + +type Node struct { + Type NodeType + Permission Permission + Nodes []Node +} + +type PermissionCheck interface { + Authorize(ctx context.Context, req *AuthorizationRequest) (*AuthorizationResponse, error) + AuthorizeMember(ctx context.Context, repoID uuid.UUID, req *AuthorizationRequest) (*AuthorizationResponse, error) +} + +var _ PermissionCheck = (*RbacAuth)(nil) + +type RbacAuth struct { //nolint + db models.IRepo +} + +func NewRbacAuth(IRepo models.IRepo) *RbacAuth { + return &RbacAuth{db: IRepo} +} + +func (s *RbacAuth) InitRbac(ctx context.Context, adminUser *models.User) error { + all := []rbacmodel.Resource{rbacmodel.All} + return s.db.Transaction(ctx, func(repo models.IRepo) error { + //add super + superGroup, err := s.addGroupPolicy(ctx, repo, Super, &rbacmodel.Policy{ + Name: Super, + Statements: MakeStatementForPolicyTypeOrDie("AllAccess", all), + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + //add repo admin + _, err = s.addGroupPolicy(ctx, repo, RepoAdmin, &rbacmodel.Policy{ + Name: RepoAdmin, + Statements: rbacmodel.Statements{ + { + Action: []string{ + "repo:*", + }, + Resource: rbacmodel.RepoURArn(rbacmodel.UserIDCapture, rbacmodel.RepoIDCapture), + Effect: rbacmodel.StatementEffectAllow, + }, + }, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + // add repo write + _, err = s.addGroupPolicy(ctx, repo, RepoWrite, &rbacmodel.Policy{ + Name: RepoWrite, + Statements: MakeStatementForPolicyTypeOrDie("RepoReadWrite", []rbacmodel.Resource{rbacmodel.RepoURArn(rbacmodel.UserIDCapture, rbacmodel.RepoIDCapture)}), + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + // add repo read + _, err = s.addGroupPolicy(ctx, repo, RepoRead, &rbacmodel.Policy{ + Name: RepoRead, + Statements: MakeStatementForPolicyTypeOrDie("RepoRead", []rbacmodel.Resource{rbacmodel.RepoURArn(rbacmodel.UserIDCapture, rbacmodel.RepoIDCapture)}), + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + userOwner := MakeStatementForPolicyTypeOrDie("UserFullAccess", []rbacmodel.Resource{ + rbacmodel.UserArn(rbacmodel.UserIDCapture), + rbacmodel.UserAkskArn(rbacmodel.UserIDCapture), + }) + _, err = s.addGroupPolicy(ctx, repo, UserOwnAccess, &rbacmodel.Policy{ + Name: UserOwnAccess, + Statements: append(userOwner, rbacmodel.Statement{ + Action: []string{ + rbacmodel.CreateRepositoryAction, + "repo:*", + }, + Resource: rbacmodel.RepoUArn(rbacmodel.UserIDCapture), + Effect: rbacmodel.StatementEffectAllow, + }), + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + adminUser, err = repo.UserRepo().Insert(ctx, adminUser) + if err != nil { + return err + } + + _, err = repo.UserGroupRepo().Insert(ctx, &rbacmodel.UserGroup{ + UserID: adminUser.ID, + GroupID: superGroup.ID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + return err + }) +} + +func (s *RbacAuth) addGroupPolicy(ctx context.Context, repo models.IRepo, groupName string, policies ...*rbacmodel.Policy) (*rbacmodel.Group, error) { + var policyIds []uuid.UUID + for _, policy := range policies { + _, err := repo.PolicyRepo().Insert(ctx, policy) + if err != nil { + return nil, err + } + policyIds = append(policyIds, policy.ID) + } + + return repo.GroupRepo().Insert(ctx, &rbacmodel.Group{ + Name: groupName, + Policies: policyIds, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) +} + +type AuthorizationRequest struct { + OperatorID uuid.UUID + RequiredPermissions Node +} + +type AuthorizationResponse struct { + Allowed bool + Error error +} + +func (s *RbacAuth) listEffectivePolicies(ctx context.Context, userID uuid.UUID) ([]*rbacmodel.Policy, error) { + group, err := s.db.GroupRepo().GetGroupByUserID(ctx, userID) + if err != nil { + return nil, err + } + + //get group policy + policies, err := s.db.PolicyRepo().List(ctx, rbacmodel.NewListPolicyParams().SetIDs(group.Policies...)) + if err != nil { + return nil, err + } + return policies, err +} + +func (s *RbacAuth) Authorize(ctx context.Context, req *AuthorizationRequest) (*AuthorizationResponse, error) { + policies, err := s.listEffectivePolicies(ctx, req.OperatorID) + if err != nil { + return nil, err + } + + allowed := checkPermissions(ctx, req.RequiredPermissions, ResourceParams{UserID: req.OperatorID}, policies) + + if allowed != CheckAllow { + return &AuthorizationResponse{ + Allowed: false, + Error: ErrInsufficientPermissions, + }, nil + } + + // we're allowed! + return &AuthorizationResponse{Allowed: true}, nil +} + +type ResourceParams struct { + UserID uuid.UUID + RepoID uuid.UUID +} + +func (rp ResourceParams) Render(resource rbacmodel.Resource) rbacmodel.Resource { + if rp.UserID != uuid.Nil { + resource = resource.WithUserID(rp.UserID.String()) + } + + if rp.RepoID != uuid.Nil { + resource = resource.WithRepoID(rp.RepoID.String()) + } + return resource +} + +func (s *RbacAuth) getMemberPolicy(ctx context.Context, operatorID uuid.UUID, repoID uuid.UUID) ([]*rbacmodel.Policy, error) { + member, err := s.db.MemberRepo().GetMember(ctx, models.NewGetMemberParams().SetUserID(operatorID).SetRepoID(repoID)) + if err != nil { + return nil, err + } + + group, err := s.db.GroupRepo().Get(ctx, rbacmodel.NewGetGroupParams().SetID(member.GroupID)) + if err != nil { + return nil, err + } + + policy, err := s.db.PolicyRepo().List(ctx, rbacmodel.NewListPolicyParams().SetIDs(group.Policies...)) + if err != nil { + return nil, err + } + return policy, err +} + +func (s *RbacAuth) AuthorizeMember(ctx context.Context, repoID uuid.UUID, req *AuthorizationRequest) (*AuthorizationResponse, error) { + repo, err := s.db.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetID(repoID)) + if err != nil { + return nil, err + } + + if repo.OwnerID == req.OperatorID { + //owner has all permission + return &AuthorizationResponse{Allowed: true}, nil + } + + policies, err := s.getMemberPolicy(ctx, req.OperatorID, repoID) + if err != nil { + if errors.Is(err, models.ErrNotFound) { + return &AuthorizationResponse{ + Allowed: false, + Error: ErrInsufficientPermissions, + }, nil + } + return nil, err + } + + resourceParams := ResourceParams{UserID: repo.OwnerID, RepoID: repoID} + allowed := checkPermissions(ctx, req.RequiredPermissions, resourceParams, policies) + + if allowed != CheckAllow { + return &AuthorizationResponse{ + Allowed: false, + Error: ErrInsufficientPermissions, + }, nil + } + + // we're allowed! + return &AuthorizationResponse{Allowed: true}, nil +} + +func checkPermissions(ctx context.Context, node Node, params ResourceParams, policies []*rbacmodel.Policy) CheckResult { + allowed := CheckNeutral + switch node.Type { + case NodeTypeNode: + // check whether the permission is allowed, denied or natural (not allowed and not denied) + for _, policy := range policies { + for _, stmt := range policy.Statements { + resource := params.Render(stmt.Resource) + if !ArnMatch(resource.String(), node.Permission.Resource.String()) { + continue + } + for _, action := range stmt.Action { + if !wildcard.Match(action, node.Permission.Action) { + continue // not a matching action + } + + if stmt.Effect == rbacmodel.StatementEffectDeny { + // this is a "Deny" and it takes precedence + return CheckDeny + } + + allowed = CheckAllow + } + } + } + + case NodeTypeOr: + // returns: + // Allowed - at least one of the permissions is allowed and no one is denied + // Denied - one of the permissions is Deny + // Natural - otherwise + for _, node := range node.Nodes { + result := checkPermissions(ctx, node, params, policies) + if result == CheckDeny { + return CheckDeny + } + if allowed != CheckAllow { + allowed = result + } + } + + case NodeTypeAnd: + // returns: + // Allowed - all the permissions are allowed + // Denied - one of the permissions is Deny + // Natural - otherwise + for _, node := range node.Nodes { + result := checkPermissions(ctx, node, params, policies) + if result == CheckNeutral || result == CheckDeny { + return result + } + } + return CheckAllow + + default: + return CheckDeny + } + return allowed +} diff --git a/auth/rbac/rbac_test.go b/auth/rbac/rbac_test.go new file mode 100644 index 00000000..259876d9 --- /dev/null +++ b/auth/rbac/rbac_test.go @@ -0,0 +1,295 @@ +package rbac_test + +import ( + "context" + "testing" + "time" + + "github.com/google/uuid" + + "github.com/stretchr/testify/require" + + "github.com/jiaozifs/jiaozifs/auth" + "github.com/jiaozifs/jiaozifs/models/rbacmodel" + + "github.com/jiaozifs/jiaozifs/auth/rbac" + "github.com/jiaozifs/jiaozifs/models" + + "github.com/jiaozifs/jiaozifs/testhelper" +) + +func TestNewRbac(t *testing.T) { + ctx := context.Background() + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() + + dbRepo := models.NewRepo(db) + rbacChecker := rbac.NewRbacAuth(dbRepo) + + password, err := auth.HashPassword("123456789") + require.NoError(t, err) + superUser := &models.User{ + Name: "admin", + Email: "", + EncryptedPassword: string(password), + CurrentSignInAt: time.Now(), + LastSignInAt: time.Now(), + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + err = rbacChecker.InitRbac(ctx, superUser) + require.NoError(t, err) + + addCommonUser := func(name string) *models.User { + commonUser := &models.User{ + Name: name, + Email: name + "@test.com", + EncryptedPassword: string(password), + CurrentSignInAt: time.Now(), + LastSignInAt: time.Now(), + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + commonUser, err = dbRepo.UserRepo().Insert(ctx, commonUser) + require.NoError(t, err) + + userOwnGroup, err := dbRepo.GroupRepo().Get(ctx, rbacmodel.NewGetGroupParams().SetName(rbac.UserOwnAccess)) + require.NoError(t, err) + //bind own user group + _, err = dbRepo.UserGroupRepo().Insert(ctx, &rbacmodel.UserGroup{ + UserID: commonUser.ID, + GroupID: userOwnGroup.ID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + require.NoError(t, err) + return commonUser + } + + addRepo := func(name string, ownerID uuid.UUID) *models.Repository { + repo, err := dbRepo.RepositoryRepo().Insert(ctx, &models.Repository{ + Name: name, + OwnerID: ownerID, + HEAD: "master", + }) + require.NoError(t, err) + return repo + } + + t.Run("super user", func(t *testing.T) { + resp, err := rbacChecker.Authorize(ctx, &rbac.AuthorizationRequest{ + OperatorID: superUser.ID, + RequiredPermissions: rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ReadUserAction, + Resource: rbacmodel.UserArn(superUser.ID.String()), + }, + }, + }) + require.NoError(t, err) + require.NoError(t, resp.Error) + require.True(t, resp.Allowed) + }) + + t.Run("super user controller other user", func(t *testing.T) { + commonUser := &models.User{ + Name: "common", + Email: "test@test.com", + EncryptedPassword: string(password), + CurrentSignInAt: time.Now(), + LastSignInAt: time.Now(), + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + commonUser, err = dbRepo.UserRepo().Insert(ctx, commonUser) + require.NoError(t, err) + + resp, err := rbacChecker.Authorize(ctx, &rbac.AuthorizationRequest{ + OperatorID: superUser.ID, + RequiredPermissions: rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ReadUserAction, + Resource: rbacmodel.UserArn(commonUser.ID.String()), + }, + }, + }) + require.NoError(t, err) + require.NoError(t, resp.Error) + require.True(t, resp.Allowed) + }) + + t.Run("common user controller himself", func(t *testing.T) { + commonUser := addCommonUser("common1") + resp, err := rbacChecker.Authorize(ctx, &rbac.AuthorizationRequest{ + OperatorID: commonUser.ID, + RequiredPermissions: rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ReadUserAction, + Resource: rbacmodel.UserArn(commonUser.ID.String()), + }, + }, + }) + require.NoError(t, err) + require.NoError(t, resp.Error) + require.True(t, resp.Allowed) + }) + + t.Run("common user cannot controller himself", func(t *testing.T) { + commonUser := addCommonUser("common2") + resp, err := rbacChecker.Authorize(ctx, &rbac.AuthorizationRequest{ + OperatorID: commonUser.ID, + RequiredPermissions: rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ReadUserAction, + Resource: rbacmodel.UserArn(superUser.ID.String()), + }, + }, + }) + require.NoError(t, err) + require.Equal(t, rbac.ErrInsufficientPermissions, resp.Error) + require.False(t, resp.Allowed) + }) + + t.Run("create repo", func(t *testing.T) { + commonUser := addCommonUser("common3") + resp, err := rbacChecker.Authorize(ctx, &rbac.AuthorizationRequest{ + OperatorID: commonUser.ID, + RequiredPermissions: rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.CreateRepositoryAction, + Resource: rbacmodel.RepoUArn(commonUser.ID.String()), + }, + }, + }) + require.NoError(t, err) + require.NoError(t, resp.Error) + require.True(t, resp.Allowed) + }) + + t.Run("create branch in own user", func(t *testing.T) { + repoID := uuid.New() + commonUser := addCommonUser("common4") + + resp, err := rbacChecker.Authorize(ctx, &rbac.AuthorizationRequest{ + OperatorID: commonUser.ID, + RequiredPermissions: rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.CreateBranchAction, + Resource: rbacmodel.RepoURArn(commonUser.ID.String(), repoID.String()), + }, + }, + }) + require.NoError(t, err) + require.NoError(t, resp.Error) + require.True(t, resp.Allowed) + }) + + t.Run("super create others repo", func(t *testing.T) { + commonUser := addCommonUser("common5") + resp, err := rbacChecker.Authorize(ctx, &rbac.AuthorizationRequest{ + OperatorID: superUser.ID, + RequiredPermissions: rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.CreateRepositoryAction, + Resource: rbacmodel.RepoUArn(commonUser.ID.String()), + }, + }, + }) + require.NoError(t, err) + require.NoError(t, resp.Error) + require.True(t, resp.Allowed) + }) + + t.Run("cannt create branch in other user", func(t *testing.T) { + commonUser := addCommonUser("common6") + other1User := addCommonUser("other1") + repo := addRepo("aaa", other1User.ID) + resp, err := rbacChecker.AuthorizeMember(ctx, repo.ID, &rbac.AuthorizationRequest{ + OperatorID: commonUser.ID, + RequiredPermissions: rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.CreateBranchAction, + Resource: rbacmodel.RepoURArn(other1User.ID.String(), repo.ID.String()), + }, + }, + }) + require.NoError(t, err) + require.Equal(t, rbac.ErrInsufficientPermissions, resp.Error) + require.False(t, resp.Allowed) + }) + + t.Run("can create branch in other user after add in member", func(t *testing.T) { + commonUser := addCommonUser("common7") + other1User := addCommonUser("other2") + repo := addRepo("aaa", other1User.ID) + repoWriteGroup, err := dbRepo.GroupRepo().Get(ctx, rbacmodel.NewGetGroupParams().SetName(rbac.RepoWrite)) + require.NoError(t, err) + _, err = dbRepo.MemberRepo().Insert(ctx, &models.Member{ + UserID: commonUser.ID, + RepoID: repo.ID, + GroupID: repoWriteGroup.ID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + require.NoError(t, err) + + resp, err := rbacChecker.AuthorizeMember(ctx, repo.ID, &rbac.AuthorizationRequest{ + OperatorID: commonUser.ID, + RequiredPermissions: rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.CreateBranchAction, + Resource: rbacmodel.RepoURArn(other1User.ID.String(), repo.ID.String()), + }, + }, + }) + require.NoError(t, err) + require.NoError(t, resp.Error) + require.True(t, resp.Allowed) + }) + + t.Run("can not create branch in other user after add in member", func(t *testing.T) { + commonUser := addCommonUser("common8") + other1User := addCommonUser("other3") + repo := addRepo("aaa", other1User.ID) + repoWriteGroup, err := dbRepo.GroupRepo().Get(ctx, rbacmodel.NewGetGroupParams().SetName(rbac.RepoRead)) + require.NoError(t, err) + _, err = dbRepo.MemberRepo().Insert(ctx, &models.Member{ + UserID: commonUser.ID, + RepoID: repo.ID, + GroupID: repoWriteGroup.ID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + require.NoError(t, err) + + resp, err := rbacChecker.AuthorizeMember(ctx, repo.ID, &rbac.AuthorizationRequest{ + OperatorID: commonUser.ID, + RequiredPermissions: rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.CreateBranchAction, + Resource: rbacmodel.RepoURArn(other1User.ID.String(), repo.ID.String()), + }, + }, + }) + require.NoError(t, err) + require.Equal(t, rbac.ErrInsufficientPermissions, resp.Error) + require.False(t, resp.Allowed) + }) + + t.Run("read own branch", func(t *testing.T) { + commonUser := addCommonUser("common9") + repo := addRepo("aaa", commonUser.ID) + resp, err := rbacChecker.AuthorizeMember(ctx, repo.ID, &rbac.AuthorizationRequest{ + OperatorID: commonUser.ID, + RequiredPermissions: rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ReadBranchAction, + Resource: rbacmodel.RepoURArn(commonUser.ID.String(), repo.ID.String()), + }, + }, + }) + require.NoError(t, err) + require.NoError(t, resp.Error) + require.True(t, resp.Allowed) + }) +} diff --git a/auth/rbac/statements.go b/auth/rbac/statements.go new file mode 100644 index 00000000..2232d15c --- /dev/null +++ b/auth/rbac/statements.go @@ -0,0 +1,150 @@ +package rbac + +import ( + "errors" + "fmt" + + "github.com/jiaozifs/jiaozifs/models/rbacmodel" +) + +var ( + ErrStatementNotFound = errors.New("statement not found") +) + +// statementForPolicyType holds the Statement for a policy by its name, +// without the required ARN. +var statementByName = map[string]rbacmodel.Statement{ + "AllAccess": { + Action: []string{"repo:*", "auth:*", "user:*"}, + Effect: rbacmodel.StatementEffectAllow, + }, + "RepoReadWrite": { + Action: []string{ + rbacmodel.ReadRepositoryAction, + rbacmodel.UpdateRepositoryAction, + rbacmodel.DeleteRepositoryAction, + rbacmodel.ListRepositoriesAction, + + rbacmodel.ReadObjectAction, + rbacmodel.WriteObjectAction, + rbacmodel.DeleteObjectAction, + rbacmodel.ListObjectsAction, + + rbacmodel.CreateCommitAction, + rbacmodel.ReadCommitAction, + rbacmodel.ListCommitsAction, + + rbacmodel.CreateBranchAction, + rbacmodel.DeleteBranchAction, + rbacmodel.ReadBranchAction, + rbacmodel.ListBranchesAction, + rbacmodel.WriteBranchAction, + rbacmodel.DeleteWipAction, + + rbacmodel.CreateMergeRequestAction, + rbacmodel.ReadMergeRequestAction, + rbacmodel.UpdateMergeRequestAction, + rbacmodel.ListMergeRequestAction, + + rbacmodel.ReadConfigAction, + rbacmodel.WriteConfigAction, + + rbacmodel.ReadWipAction, + rbacmodel.ListWipAction, + rbacmodel.WriteWipAction, + rbacmodel.CreateWipAction, + }, + Effect: rbacmodel.StatementEffectAllow, + }, + "RepoRead": { + Action: []string{ + "repo:Read*", + "repo:List*", + }, + Effect: rbacmodel.StatementEffectAllow, + }, + "RepoReadConfig": { + Action: []string{ + rbacmodel.ReadConfigAction, + }, + Effect: rbacmodel.StatementEffectAllow, + }, + "RepoWriteConfig": { + Action: []string{ + rbacmodel.ReadConfigAction, + rbacmodel.WriteConfigAction, + + rbacmodel.AddGroupMemberAction, + rbacmodel.RemoveGroupMemberAction, + }, + Effect: rbacmodel.StatementEffectAllow, + }, + "RepoMemberRead": { + Action: []string{ + rbacmodel.GetGroupMemberAction, + rbacmodel.ListGroupMemberAction, + }, + Effect: rbacmodel.StatementEffectAllow, + }, + "RepoMemberAccess": { + Action: []string{ + rbacmodel.GetGroupMemberAction, + rbacmodel.ListGroupMemberAction, + rbacmodel.AddGroupMemberAction, + rbacmodel.RemoveGroupMemberAction, + }, + Effect: rbacmodel.StatementEffectAllow, + }, + "UserFullAccess": { + Action: []string{ + "auth:*", + "user:*", + }, + + Effect: rbacmodel.StatementEffectAllow, + }, +} + +// GetActionsForPolicyType returns the actions for police type typ. +func GetActionsForPolicyType(typ string) ([]string, error) { + statement, ok := statementByName[typ] + if !ok { + return nil, fmt.Errorf("%w: %s", ErrStatementNotFound, typ) + } + actions := make([]string, len(statement.Action)) + copy(actions, statement.Action) + return actions, nil +} + +func GetActionsForPolicyTypeOrDie(typ string) []string { + ret, err := GetActionsForPolicyType(typ) + if err != nil { + panic(err) + } + return ret +} + +// MakeStatementForPolicyType returns statements for policy type typ, +// limited to resources. +func MakeStatementForPolicyType(typ string, resources []rbacmodel.Resource) (rbacmodel.Statements, error) { + statement, ok := statementByName[typ] + if !ok { + return nil, fmt.Errorf("%w: %s", ErrStatementNotFound, typ) + } + statements := make(rbacmodel.Statements, len(resources)) + for i, resource := range resources { + if statement.Resource == "" { + statements[i] = statement + statements[i].Resource = resource + } + } + return statements, nil +} + +func MakeStatementForPolicyTypeOrDie(typ string, resources []rbacmodel.Resource) rbacmodel.Statements { + statements, err := MakeStatementForPolicyType(typ, resources) + if err != nil { + panic(err) + } + return statements +} diff --git a/auth/rbac/wildcard/match.go b/auth/rbac/wildcard/match.go new file mode 100644 index 00000000..624c440f --- /dev/null +++ b/auth/rbac/wildcard/match.go @@ -0,0 +1,89 @@ +package wildcard + +import ( + "unicode/utf8" +) + +// Match reports whether name matches the shell pattern. +// This is a strip down version of Go's `path.Match` https://pkg.go.dev/path#Match +// Call a "fixword" a maximal portion of the pattern consisting only of regular characters and ?s. +// So a fixword has to begin after * or at the beginning of the string, and it has to end before * or at the end of the string. +// Each fixword matches a fixed length of string. Now a pattern is a list of fixwords separated by *s. +// Consider a fixword that is not preceded by a *; that's an easy match to find because it can only be at one place. +// Consider a fixword that is preceded by a *; if it matches at multiple places then it is always safe to match it at +// the first possible location: either the pattern ends after that fixword in which case there's only one possible location, +// or the pattern continues with *, in which case that * can "expand" to pick up all characters and the next match of the fixword. +func Match(pattern, name string) bool { +Pattern: + for len(pattern) > 0 { + var ( + star bool + chunk string + ) + star, chunk, pattern = scanChunk(pattern) + if star && chunk == "" { + // Trailing * matches rest of string + return true + } + // Look for match at current position. + t, ok := matchChunk(chunk, name) + // if we're the last chunk, make sure we've exhausted the name + // otherwise we'll give a false result even if we could still match + // using the star + if ok && (len(t) == 0 || len(pattern) > 0) { + name = t + continue + } + if star { + // Look for match skipping i+1 bytes. + for i := 0; i < len(name); i++ { + t, ok := matchChunk(chunk, name[i+1:]) + if ok { + // if we're the last chunk, make sure we exhausted the name + if len(pattern) == 0 && len(t) > 0 { + continue + } + name = t + continue Pattern + } + } + } + return false + } + return len(name) == 0 +} + +// scanChunk gets the next segment of pattern, which is a non-star string +// possibly preceded by a star. +func scanChunk(pattern string) (star bool, chunk, rest string) { + for len(pattern) > 0 && pattern[0] == '*' { + pattern = pattern[1:] + star = true + } + for i := 1; i < len(pattern); i++ { + if pattern[i] == '*' { + return star, pattern[0:i], pattern[i:] + } + } + return star, pattern, "" +} + +// matchChunk checks whether chunk matches the beginning of s. +// If so, it returns the remainder of s (after the match). +// Chunk is all single-character operators: literals, char classes, and ?. +func matchChunk(chunk, s string) (rest string, ok bool) { + for len(chunk) > 0 { + if len(s) == 0 { + return "", false + } + n := 1 + if chunk[0] == '?' { + _, n = utf8.DecodeRuneInString(s) + } else if chunk[0] != s[0] { + return "", false + } + s = s[n:] + chunk = chunk[1:] + } + return s, true +} diff --git a/auth/rbac/wildcard/match_test.go b/auth/rbac/wildcard/match_test.go new file mode 100644 index 00000000..d2d38aa7 --- /dev/null +++ b/auth/rbac/wildcard/match_test.go @@ -0,0 +1,302 @@ +package wildcard_test + +/* + * MinIO Cloud Storage, (C) 2015, 2016 MinIO, Inc. + * + * 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. + */ + +import ( + "testing" + + "github.com/jiaozifs/jiaozifs/auth/rbac/wildcard" +) + +// TestMatch - Tests validate the logic of wild card matching. +// `Match` supports '*' and '?' wildcards. +// Sample usage: In resource matching for bucket policy validation. +func TestMatch(t *testing.T) { + tests := []struct { + pattern string + text string + matched bool + }{ + { + pattern: "*", + text: "s3:GetObject", + matched: true, + }, + { + pattern: "", + text: "s3:GetObject", + matched: false, + }, + { + pattern: "", + text: "", + matched: true, + }, + { + pattern: "s3:*", + text: "s3:ListMultipartUploadParts", + matched: true, + }, + { + pattern: "s3:ListBucketMultipartUploads", + text: "s3:ListBucket", + matched: false, + }, + { + pattern: "s3:ListBucket", + text: "s3:ListBucket", + matched: true, + }, + { + pattern: "s3:ListBucketMultipartUploads", + text: "s3:ListBucketMultipartUploads", + matched: true, + }, + { + pattern: "my-bucket/oo*", + text: "my-bucket/oo", + matched: true, + }, + { + pattern: "my-bucket/In*", + text: "my-bucket/India/Karnataka/", + matched: true, + }, + { + pattern: "my-bucket/In*", + text: "my-bucket/Karnataka/India/", + matched: false, + }, + { + pattern: "my-bucket/In*/Ka*/Ban", + text: "my-bucket/India/Karnataka/Ban", + matched: true, + }, + { + pattern: "my-bucket/In*/Ka*/Ban", + text: "my-bucket/India/Karnataka/Ban/Ban/Ban/Ban/Ban", + matched: true, + }, + { + pattern: "my-bucket/In*/Ka*/Ban", + text: "my-bucket/India/Karnataka/Area1/Area2/Area3/Ban", + matched: true, + }, + { + pattern: "my-bucket/In*/Ka*/Ban", + text: "my-bucket/India/State1/State2/Karnataka/Area1/Area2/Area3/Ban", + matched: true, + }, + { + pattern: "my-bucket/In*/Ka*/Ban", + text: "my-bucket/India/Karnataka/Bangalore", + matched: false, + }, + { + pattern: "my-bucket/In*/Ka*/Ban*", + text: "my-bucket/India/Karnataka/Bangalore", + matched: true, + }, + { + pattern: "my-bucket/*", + text: "my-bucket/India", + matched: true, + }, + { + pattern: "my-bucket/oo*", + text: "my-bucket/odo", + matched: false, + }, + { + pattern: "my-bucket?/abc*", + text: "mybucket/abc", + matched: false, + }, + { + pattern: "my-bucket?/abc*", + text: "my-bucket1/abc", + matched: true, + }, + { + pattern: "my-?-bucket/abc*", + text: "my--bucket/abc", + matched: false, + }, + { + pattern: "my-?-bucket/abc*", + text: "my-1-bucket/abc", + matched: true, + }, + { + pattern: "my-?-bucket/abc*", + text: "my-k-bucket/abc", + matched: true, + }, + { + pattern: "my??bucket/abc*", + text: "mybucket/abc", + matched: false, + }, + { + pattern: "my??bucket/abc*", + text: "my4abucket/abc", + matched: true, + }, + { + pattern: "my-bucket?abc*", + text: "my-bucket/abc", + matched: true, + }, + { + pattern: "my-bucket/abc?efg", + text: "my-bucket/abcdefg", + matched: true, + }, + { + pattern: "my-bucket/abc?efg", + text: "my-bucket/abc/efg", + matched: true, + }, + { + pattern: "my-bucket/abc????", + text: "my-bucket/abc", + matched: false, + }, + { + pattern: "my-bucket/abc????", + text: "my-bucket/abcde", + matched: false, + }, + { + pattern: "my-bucket/abc????", + text: "my-bucket/abcdefg", + matched: true, + }, + { + pattern: "my-bucket/abc?", + text: "my-bucket/abc", + matched: false, + }, + { + pattern: "my-bucket/abc?", + text: "my-bucket/abcd", + matched: true, + }, + { + pattern: "my-bucket/abc?", + text: "my-bucket/abcde", + matched: false, + }, + { + pattern: "my-bucket/mnop*?", + text: "my-bucket/mnop", + matched: false, + }, + { + pattern: "my-bucket/mnop*?", + text: "my-bucket/mnopqrst/mnopqr", + matched: true, + }, + { + pattern: "my-bucket/mnop*?", + text: "my-bucket/mnopqrst/mnopqrs", + matched: true, + }, + { + pattern: "my-bucket/mnop*?", + text: "my-bucket/mnop", + matched: false, + }, + { + pattern: "my-bucket/mnop*?", + text: "my-bucket/mnopq", + matched: true, + }, + { + pattern: "my-bucket/mnop*?", + text: "my-bucket/mnopqr", + matched: true, + }, + { + pattern: "my-bucket/mnop*?and", + text: "my-bucket/mnopqand", + matched: true, + }, + { + pattern: "my-bucket/mnop*?and", + text: "my-bucket/mnopand", + matched: false, + }, + { + pattern: "my-bucket/mnop*?and", + text: "my-bucket/mnopqand", + matched: true, + }, + { + pattern: "my-bucket/mnop*?", + text: "my-bucket/mn", + matched: false, + }, + { + pattern: "my-bucket/mnop*?", + text: "my-bucket/mnopqrst/mnopqrs", + matched: true, + }, + { + pattern: "my-bucket/mnop*??", + text: "my-bucket/mnopqrst", + matched: true, + }, + { + pattern: "my-bucket/mnop*qrst", + text: "my-bucket/mnopabcdegqrst", + matched: true, + }, + { + pattern: "my-bucket/mnop*?and", + text: "my-bucket/mnopqand", + matched: true, + }, + { + pattern: "my-bucket/mnop*?and", + text: "my-bucket/mnopand", + matched: false, + }, + { + pattern: "my-bucket/mnop*?and?", + text: "my-bucket/mnopqanda", + matched: true, + }, + { + pattern: "my-bucket/mnop*?and", + text: "my-bucket/mnopqanda", + matched: false, + }, + { + pattern: "my-?-bucket/abc*", + text: "my-bucket/mnopqanda", + matched: false, + }, + } + for i, tt := range tests { + actualResult := wildcard.Match(tt.pattern, tt.text) + if tt.matched != actualResult { + t.Errorf("Match('%s', '%s') [%d] expected=%t, got=%t", + tt.pattern, tt.text, i+1, tt.matched, actualResult) + } + } +} diff --git a/auth/types.go b/auth/types.go index 0262186e..6ce93b51 100644 --- a/auth/types.go +++ b/auth/types.go @@ -1,8 +1,16 @@ package auth -import "time" +import ( + "time" + + "golang.org/x/crypto/bcrypt" +) const ( ExpirationDuration = time.Hour - passwordCost = 12 + PasswordCost = 12 ) + +func HashPassword(password string) ([]byte, error) { + return bcrypt.GenerateFromPassword([]byte(password), PasswordCost) +} diff --git a/block/local/adapter.go b/block/local/adapter.go index 66e1fa57..32740562 100644 --- a/block/local/adapter.go +++ b/block/local/adapter.go @@ -157,7 +157,6 @@ func (l *Adapter) Path() string { func (l *Adapter) Put(_ context.Context, obj block.ObjectPointer, _ int64, reader io.Reader, _ block.PutOpts) error { p, err := l.extractParamsFromObj(obj) - fmt.Println(p) if err != nil { return err } diff --git a/cmd/daemon.go b/cmd/daemon.go index 944c1129..5db0167a 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -3,6 +3,8 @@ package cmd import ( "context" + "github.com/jiaozifs/jiaozifs/auth/rbac" + "github.com/jiaozifs/jiaozifs/auth/aksk" "github.com/pelletier/go-toml/v2" @@ -69,6 +71,11 @@ var daemonCmd = &cobra.Command{ }), fx_opt.Override(fx_opt.NextInvoke(), migrations.MigrateDatabase), + //permission + fx_opt.Override(new(rbac.PermissionCheck), func(repo models.IRepo) rbac.PermissionCheck { + return rbac.NewRbacAuth(repo) + }), + //api fx_opt.Override(new(crypt.SecretStore), auth.NewSectetStore), fx_opt.Override(new(sessions.Store), auth.NewSessionStore), diff --git a/cmd/init.go b/cmd/init.go index f1f1010a..cced892e 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -1,7 +1,19 @@ package cmd import ( + "fmt" "os" + "time" + + "github.com/jiaozifs/jiaozifs/models/migrations" + + "github.com/jiaozifs/jiaozifs/auth/rbac" + "github.com/jiaozifs/jiaozifs/controller/validator" + + "github.com/jiaozifs/jiaozifs/auth" + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/utils" + "github.com/m1/go-generate-password/generator" "github.com/jiaozifs/jiaozifs/config" "github.com/spf13/cobra" @@ -41,16 +53,88 @@ var initCmd = &cobra.Command{ if cfg.Blockstore.Type == "local" { _, err = os.Stat(cfg.Blockstore.Local.Path) if os.IsNotExist(err) { - return os.MkdirAll(cfg.Blockstore.Local.Path, 0755) + err = os.MkdirAll(cfg.Blockstore.Local.Path, 0755) + if err != nil { + return err + } } } - return nil + + return initRbac(cmd, &cfg.Database) }, } +func initRbac(cmd *cobra.Command, cfg *config.DatabaseConfig) error { + bunDB, err := models.NewBunDBFromConfig(cmd.Context(), cfg) + if err != nil { + return err + } + + err = migrations.MigrateDatabase(cmd.Context(), bunDB) + if err != nil { + return err + } + + repo := models.NewRepo(bunDB) + userName, err := cmd.Flags().GetString("super_username") + if err != nil { + return err + } + + err = validator.ValidateUsername(userName) + if err != nil { + return err + } + + password, err := cmd.Flags().GetString("super_password") + if err != nil { + return err + } + + if len(password) == 0 { + config := generator.Config{ + Length: 16, + IncludeSymbols: false, + IncludeNumbers: true, + IncludeLowercaseLetters: true, + IncludeUppercaseLetters: true, + ExcludeSimilarCharacters: true, + ExcludeAmbiguousCharacters: true, + } + g, err := generator.New(&config) + if err != nil { + return err + } + + pwd, err := g.Generate() + if err != nil { + return err + } + password = utils.StringValue(pwd) + } + fmt.Println("super user:", userName, password) + passwordHash, err := auth.HashPassword(password) + password = "" + if err != nil { + return err + } + return rbac.NewRbacAuth(repo).InitRbac(cmd.Context(), &models.User{ + Name: userName, + Email: "", + EncryptedPassword: string(passwordHash), + CurrentSignInAt: time.Now(), + LastSignInAt: time.Now(), + CurrentSignInIP: "", + LastSignInIP: "", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) +} func init() { rootCmd.AddCommand(initCmd) initCmd.Flags().String("bs_path", config.DefaultLocalBSPath, "config blockstore path") initCmd.Flags().Bool("db_debug", false, "enable database debug") + initCmd.Flags().String("super_username", "admin", "super name") + initCmd.Flags().String("super_password", "", "super user name if not specific, random generate one") initCmd.Flags().String("db", "", "pg connection string eg. postgres://user:pass@localhost:5432/jiaozifs?sslmode=disable") } diff --git a/controller/aksk_ctl.go b/controller/aksk_ctl.go index 6e65133e..e674fe42 100644 --- a/controller/aksk_ctl.go +++ b/controller/aksk_ctl.go @@ -5,6 +5,10 @@ import ( "net/http" "time" + "github.com/jiaozifs/jiaozifs/models/rbacmodel" + + "github.com/jiaozifs/jiaozifs/auth/rbac" + "github.com/jiaozifs/jiaozifs/utils" aksk2 "github.com/jiaozifs/jiaozifs/auth/aksk" @@ -18,6 +22,7 @@ import ( type AkSkController struct { fx.In + BaseController Repo models.IRepo } @@ -29,6 +34,15 @@ func (akskCtl AkSkController) CreateAksk(ctx context.Context, w *api.JiaozifsRes return } + if !akskCtl.authorize(ctx, w, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.CreateCredentialsAction, + Resource: rbacmodel.UserAkskArn(operator.ID.String()), + }, + }) { + return + } + ak, sk, err := aksk2.GenerateAksk() if err != nil { w.Error(err) @@ -48,7 +62,7 @@ func (akskCtl AkSkController) CreateAksk(ctx context.Context, w *api.JiaozifsRes w.Error(err) return } - w.JSON(akskToDto(aksk), http.StatusCreated) + w.JSON(utils.Silent(akskToDto(aksk)), http.StatusCreated) } func (akskCtl AkSkController) GetAksk(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, params api.GetAkskParams) { @@ -58,6 +72,15 @@ func (akskCtl AkSkController) GetAksk(ctx context.Context, w *api.JiaozifsRespon return } + if !akskCtl.authorize(ctx, w, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ReadCredentialsAction, + Resource: rbacmodel.UserAkskArn(operator.ID.String()), + }, + }) { + return + } + getParams := models.NewGetAkSkParams().SetUserID(operator.ID) if params.Id != nil { getParams.SetID(*params.Id) @@ -72,7 +95,7 @@ func (akskCtl AkSkController) GetAksk(ctx context.Context, w *api.JiaozifsRespon w.Error(err) return } - w.JSON(akskToDto(aksk)) + w.JSON(utils.Silent(akskToDto(aksk))) } func (akskCtl AkSkController) DeleteAksk(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, params api.DeleteAkskParams) { @@ -82,6 +105,15 @@ func (akskCtl AkSkController) DeleteAksk(ctx context.Context, w *api.JiaozifsRes return } + if !akskCtl.authorize(ctx, w, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.DeleteCredentialsAction, + Resource: rbacmodel.UserAkskArn(operator.ID.String()), + }, + }) { + return + } + delParams := models.NewDeleteAkSkParams().SetUserID(operator.ID) if params.Id != nil { delParams.SetID(*params.Id) @@ -106,6 +138,15 @@ func (akskCtl AkSkController) ListAksks(ctx context.Context, w *api.JiaozifsResp return } + if !akskCtl.authorize(ctx, w, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ListCredentialsAction, + Resource: rbacmodel.UserAkskArn(operator.ID.String()), + }, + }) { + return + } + listParams := models.NewListAkSkParams().SetUserID(operator.ID) if params.After != nil { listParams.SetAfter(time.UnixMilli(utils.Int64Value(params.After))) @@ -120,10 +161,7 @@ func (akskCtl AkSkController) ListAksks(ctx context.Context, w *api.JiaozifsResp w.Error(err) return } - results := make([]api.Aksk, 0, len(aksks)) - for _, repo := range aksks { - results = append(results, *akskToDto(repo)) - } + results := utils.Silent(utils.ArrMap(aksks, akskToDto)) pagMag := utils.PaginationFor(hasMore, results, "UpdatedAt") pagination := api.Pagination{ HasMore: pagMag.HasMore, @@ -137,13 +175,13 @@ func (akskCtl AkSkController) ListAksks(ctx context.Context, w *api.JiaozifsResp }) } -func akskToDto(in *models.AkSk) *api.Aksk { - return &api.Aksk{ +func akskToDto(in *models.AkSk) (api.Aksk, error) { + return api.Aksk{ AccessKey: in.AccessKey, CreatedAt: in.CreatedAt.UnixMilli(), Description: in.Description, Id: in.ID, SecretKey: in.SecretKey, UpdatedAt: in.UpdatedAt.UnixMilli(), - } + }, nil } diff --git a/controller/base_ctl.go b/controller/base_ctl.go new file mode 100644 index 00000000..8af4751d --- /dev/null +++ b/controller/base_ctl.go @@ -0,0 +1,75 @@ +package controller + +import ( + "context" + "net/http" + + "github.com/google/uuid" + + "github.com/jiaozifs/jiaozifs/api" + + "github.com/jiaozifs/jiaozifs/auth" + + "github.com/jiaozifs/jiaozifs/auth/rbac" + "go.uber.org/fx" +) + +type BaseController struct { + fx.In + + PermissionCheck rbac.PermissionCheck +} + +func (c *BaseController) authorize(ctx context.Context, w *api.JiaozifsResponse, perms rbac.Node) bool { + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Unauthorized() + return false + } + + resp, err := c.PermissionCheck.Authorize(ctx, &rbac.AuthorizationRequest{ + OperatorID: operator.ID, + RequiredPermissions: perms, + }) + if err != nil { + w.String(err.Error(), http.StatusInternalServerError) + return false + } + + if resp.Error != nil { + w.Code(http.StatusUnauthorized) + return false + } + if !resp.Allowed { + w.String("User does not have the required permissions", http.StatusInternalServerError) + return false + } + return true +} + +func (c *BaseController) authorizeMember(ctx context.Context, w *api.JiaozifsResponse, repoID uuid.UUID, perms rbac.Node) bool { + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Unauthorized() + return false + } + + resp, err := c.PermissionCheck.AuthorizeMember(ctx, repoID, &rbac.AuthorizationRequest{ + OperatorID: operator.ID, + RequiredPermissions: perms, + }) + if err != nil { + w.String(err.Error(), http.StatusInternalServerError) + return false + } + + if resp.Error != nil { + w.Code(http.StatusUnauthorized) + return false + } + if !resp.Allowed { + w.String("User does not have the required permissions", http.StatusInternalServerError) + return false + } + return true +} diff --git a/controller/branch_ctl.go b/controller/branch_ctl.go index 4b58e4c8..d6113680 100644 --- a/controller/branch_ctl.go +++ b/controller/branch_ctl.go @@ -5,8 +5,10 @@ import ( "errors" "net/http" + "github.com/jiaozifs/jiaozifs/auth/rbac" "github.com/jiaozifs/jiaozifs/block/params" "github.com/jiaozifs/jiaozifs/controller/validator" + "github.com/jiaozifs/jiaozifs/models/rbacmodel" "github.com/jiaozifs/jiaozifs/versionmgr" "github.com/jiaozifs/jiaozifs/api" @@ -18,32 +20,31 @@ import ( type BranchController struct { fx.In + BaseController Repo models.IRepo PublicStorageConfig params.AdapterConfig } func (bct BranchController) ListBranches(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.ListBranchesParams) { - operator, err := auth.GetOperator(ctx) + owner, err := bct.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) if err != nil { w.Error(err) return } - owner, err := bct.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + repository, err := bct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) if err != nil { w.Error(err) return } - if operator.Name != owner.Name { - w.Forbidden() - return - } - - repository, err := bct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) - if err != nil { - w.Error(err) + if !bct.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ListBranchesAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { return } @@ -111,11 +112,6 @@ func (bct BranchController) CreateBranch(ctx context.Context, w *api.JiaozifsRes return } - if operator.Name != owner.Name { - w.Forbidden() - return - } - // Get repo repository, err := bct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) if err != nil { @@ -123,6 +119,15 @@ func (bct BranchController) CreateBranch(ctx context.Context, w *api.JiaozifsRes return } + if !bct.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.CreateBranchAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { + return + } + //get source branch sourceBranch, err := bct.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetName(body.Source).SetRepositoryID(repository.ID)) if err != nil && !errors.Is(err, models.ErrNotFound) { @@ -173,11 +178,6 @@ func (bct BranchController) DeleteBranch(ctx context.Context, w *api.JiaozifsRes return } - if operator.Name != owner.Name { - w.Forbidden() - return - } - // Get repo repository, err := bct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) if err != nil { @@ -185,6 +185,15 @@ func (bct BranchController) DeleteBranch(ctx context.Context, w *api.JiaozifsRes return } + if !bct.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.DeleteBranchAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { + return + } + if params.RefName == repository.HEAD { w.BadRequest("can not delete HEAD branch") return @@ -211,23 +220,12 @@ func (bct BranchController) DeleteBranch(ctx context.Context, w *api.JiaozifsRes } func (bct BranchController) GetBranch(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.GetBranchParams) { - operator, err := auth.GetOperator(ctx) - if err != nil { - w.Error(err) - return - } - owner, err := bct.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) if err != nil { w.Error(err) return } - if operator.Name != owner.Name { - w.Forbidden() - return - } - // Get repo repository, err := bct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) if err != nil { @@ -235,6 +233,15 @@ func (bct BranchController) GetBranch(ctx context.Context, w *api.JiaozifsRespon return } + if !bct.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ReadRepositoryAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { + return + } + // Get branch ref, err := bct.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetName(params.RefName).SetRepositoryID(repository.ID)) if err != nil { diff --git a/controller/commit_ctl.go b/controller/commit_ctl.go index 54dab564..2085a53c 100644 --- a/controller/commit_ctl.go +++ b/controller/commit_ctl.go @@ -11,8 +11,10 @@ import ( "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/auth" + "github.com/jiaozifs/jiaozifs/auth/rbac" "github.com/jiaozifs/jiaozifs/block/params" "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/models/rbacmodel" "github.com/jiaozifs/jiaozifs/utils" "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/jiaozifs/jiaozifs/versionmgr" @@ -21,6 +23,7 @@ import ( type CommitController struct { fx.In + BaseController Repo models.IRepo PublicStorageConfig params.AdapterConfig @@ -45,8 +48,12 @@ func (commitCtl CommitController) GetEntriesInRef(ctx context.Context, w *api.Ji return } - if operator.Name != ownerName { //todo check permission - w.Forbidden() + if !commitCtl.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ReadObjectAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { return } @@ -158,8 +165,12 @@ func (commitCtl CommitController) CompareCommit(ctx context.Context, w *api.Jiao return } - if operator.ID != owner.ID { //todo check permission - w.Forbidden() + if !commitCtl.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ReadCommitAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { return } @@ -220,8 +231,12 @@ func (commitCtl CommitController) GetCommitChanges(ctx context.Context, w *api.J return } - if operator.ID != owner.ID { //todo check permission - w.Forbidden() + if !commitCtl.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ReadCommitAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { return } diff --git a/controller/group.go b/controller/group.go new file mode 100644 index 00000000..6a35e85f --- /dev/null +++ b/controller/group.go @@ -0,0 +1,50 @@ +package controller + +import ( + "context" + "net/http" + + "github.com/jiaozifs/jiaozifs/models/rbacmodel" + + "github.com/jiaozifs/jiaozifs/utils" + + "github.com/jiaozifs/jiaozifs/auth" + + "github.com/jiaozifs/jiaozifs/auth/rbac" + + "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/models" + "go.uber.org/fx" +) + +type GroupController struct { + fx.In + BaseController + + Repo models.IRepo +} + +func (gCtl GroupController) ListRepoGroup(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request) { + _, err := auth.GetOperator(ctx) + if err != nil { + w.Forbidden() + return + } + + groups, err := gCtl.Repo.GroupRepo().List(ctx, rbacmodel.NewListGroupParams().SetNames(rbac.RepoAdmin, rbac.RepoWrite, rbac.RepoRead)) + if err != nil { + w.Error(err) + return + } + w.JSON(utils.Silent(utils.ArrMap[*rbacmodel.Group, *api.Group](groups, groupToDto))) +} + +func groupToDto(group *rbacmodel.Group) (*api.Group, error) { + return &api.Group{ + Id: group.ID, + Name: group.Name, + Policies: group.Policies, + CreatedAt: group.CreatedAt.UnixMilli(), + UpdatedAt: group.UpdatedAt.UnixMilli(), + }, nil +} diff --git a/controller/member.go b/controller/member.go new file mode 100644 index 00000000..2cd83bd9 --- /dev/null +++ b/controller/member.go @@ -0,0 +1,163 @@ +package controller + +import ( + "context" + "net/http" + "time" + + "github.com/jiaozifs/jiaozifs/models/rbacmodel" + "github.com/jiaozifs/jiaozifs/utils" + + "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/auth/rbac" + "github.com/jiaozifs/jiaozifs/models" + "go.uber.org/fx" +) + +type MemberController struct { + fx.In + BaseController + + Repo models.IRepo +} + +func (memberCtl MemberController) UpdateMemberGroup(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.UpdateMemberGroupParams) { + owner, err := memberCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + repository, err := memberCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) + if err != nil { + w.Error(err) + return + } + if !memberCtl.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.AddGroupMemberAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { + return + } + + listMemberParams := models.NewUpdateMemberParams().SetFilterUserID(params.UserId).SetFilterRepoID(repository.ID).SetUpdateGroupID(params.GroupId) + err = memberCtl.Repo.MemberRepo().UpdateMember(ctx, listMemberParams) + if err != nil { + w.Error(err) + return + } + w.OK() +} + +func (memberCtl MemberController) InviteMember(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.InviteMemberParams) { + owner, err := memberCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + repository, err := memberCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) + if err != nil { + w.Error(err) + return + } + if !memberCtl.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.AddGroupMemberAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { + return + } + + if owner.ID == params.UserId { + w.BadRequest("not need to invite self") + } + + // todo user need to confirm? + member, err := memberCtl.Repo.MemberRepo().Insert(ctx, &models.Member{ + UserID: params.UserId, + RepoID: repository.ID, + GroupID: params.GroupId, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + w.Error(err) + return + } + w.JSON(utils.Silent(memberToDto(member)), http.StatusCreated) + +} + +func (memberCtl MemberController) RevokeMember(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.RevokeMemberParams) { + owner, err := memberCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + repository, err := memberCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) + if err != nil { + w.Error(err) + return + } + if !memberCtl.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.RemoveGroupMemberAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { + return + } + + _, err = memberCtl.Repo.MemberRepo().DeleteMember(ctx, models.NewDeleteMemberParams().SetRepoID(repository.ID).SetUserID(params.UserId)) + if err != nil { + w.Error(err) + return + } + w.OK() +} + +func (memberCtl MemberController) ListMembers(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string) { + owner, err := memberCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + repository, err := memberCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) + if err != nil { + w.Error(err) + return + } + if !memberCtl.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ListGroupMemberAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { + return + } + + listMemberParams := models.NewListMembersParams().SetRepoID(repository.ID) + members, err := memberCtl.Repo.MemberRepo().ListMember(ctx, listMemberParams) + if err != nil { + w.Error(err) + return + } + w.JSON(utils.Silent(utils.ArrMap(members, memberToDto))) +} + +func memberToDto(m *models.Member) (api.Member, error) { + return api.Member{ + CreatedAt: m.CreatedAt.UnixMilli(), + GroupId: m.GroupID, + Id: m.ID, + RepoId: m.RepoID, + UpdatedAt: m.UpdatedAt.UnixMilli(), + UserId: m.UserID, + }, nil +} diff --git a/controller/merge_request_ctl.go b/controller/merge_request_ctl.go index 76a6f9ee..b3982210 100644 --- a/controller/merge_request_ctl.go +++ b/controller/merge_request_ctl.go @@ -7,6 +7,8 @@ import ( "net/http" "time" + "github.com/jiaozifs/jiaozifs/auth/rbac" + "github.com/jiaozifs/jiaozifs/models/rbacmodel" "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/jiaozifs/jiaozifs/utils" @@ -22,18 +24,13 @@ import ( type MergeRequestController struct { fx.In + BaseController Repo models.IRepo PublicStorageConfig params.AdapterConfig } func (mrCtl MergeRequestController) ListMergeRequests(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.ListMergeRequestsParams) { - operator, err := auth.GetOperator(ctx) - if err != nil { - w.Error(err) - return - } - owner, err := mrCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) if err != nil { w.Error(err) @@ -46,8 +43,12 @@ func (mrCtl MergeRequestController) ListMergeRequests(ctx context.Context, w *ap return } - if operator.Name != owner.Name { - w.Forbidden() + if !mrCtl.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ListMergeRequestAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { return } @@ -112,12 +113,6 @@ func (mrCtl MergeRequestController) CreateMergeRequest(ctx context.Context, w *a w.Error(err) return } - - if operator.Name != owner.Name { - w.Forbidden() - return - } - // Get repo repository, err := mrCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) if err != nil { @@ -125,6 +120,15 @@ func (mrCtl MergeRequestController) CreateMergeRequest(ctx context.Context, w *a return } + if !mrCtl.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.CreateMergeRequestAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { + return + } + if body.SourceBranchName == body.TargetBranchName { w.BadRequest(fmt.Sprintf("source branch name %s and target branch name %s can not be same", body.SourceBranchName, body.SourceBranchName)) return @@ -143,9 +147,8 @@ func (mrCtl MergeRequestController) CreateMergeRequest(ctx context.Context, w *a } params := models.NewGetMergeRequestParams().SetTargetRepo(repository.ID).SetTargetBranch(targetBranch.ID).SetSourceBranch(sourceBranch.ID).SetState(models.MergeStateInit) - mr, err := mrCtl.Repo.MergeRequestRepo().Get(ctx, params) + _, err = mrCtl.Repo.MergeRequestRepo().Get(ctx, params) if err == nil { - fmt.Println(mr) w.BadRequest(fmt.Sprintf("repo %s merge request between %s and %s already exists", repositoryName, body.SourceBranchName, body.TargetBranchName)) return } @@ -227,17 +230,21 @@ func (mrCtl MergeRequestController) GetMergeRequest(ctx context.Context, w *api. return } - if operator.Name != owner.Name { - w.Forbidden() - return - } - repository, err := mrCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) if err != nil { w.Error(err) return } + if !mrCtl.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ReadMergeRequestAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { + return + } + workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repository, mrCtl.Repo, mrCtl.PublicStorageConfig) if err != nil { w.Error(err) @@ -297,25 +304,24 @@ func (mrCtl MergeRequestController) GetMergeRequest(ctx context.Context, w *api. } func (mrCtl MergeRequestController) UpdateMergeRequest(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.UpdateMergeRequestJSONRequestBody, ownerName string, repositoryName string, mrSeq uint64) { - operator, err := auth.GetOperator(ctx) + owner, err := mrCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) if err != nil { w.Error(err) return } - owner, err := mrCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + repository, err := mrCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) if err != nil { w.Error(err) return } - if operator.Name != owner.Name { - w.Forbidden() - return - } - repository, err := mrCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) - if err != nil { - w.Error(err) + if !mrCtl.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.UpdateMergeRequestAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { return } @@ -351,11 +357,6 @@ func (mrCtl MergeRequestController) Merge(ctx context.Context, w *api.JiaozifsRe return } - if operator.Name != owner.Name { - w.Forbidden() - return - } - // Get repo repository, err := mrCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) if err != nil { @@ -363,6 +364,15 @@ func (mrCtl MergeRequestController) Merge(ctx context.Context, w *api.JiaozifsRe return } + if !mrCtl.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.MergeMergeRequestAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { + return + } + mergeRequest, err := mrCtl.Repo.MergeRequestRepo().Get(ctx, models.NewGetMergeRequestParams().SetTargetRepo(repository.ID).SetNumber(mrSeq)) if err != nil { w.Error(err) diff --git a/controller/object_ctl.go b/controller/object_ctl.go index db0af09a..96513e68 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -10,6 +10,7 @@ import ( "net/http" "time" + "github.com/jiaozifs/jiaozifs/auth/rbac" "github.com/jiaozifs/jiaozifs/controller/validator" "github.com/go-openapi/swag" @@ -19,6 +20,7 @@ import ( "github.com/jiaozifs/jiaozifs/block/params" "github.com/jiaozifs/jiaozifs/models" "github.com/jiaozifs/jiaozifs/models/filemode" + "github.com/jiaozifs/jiaozifs/models/rbacmodel" "github.com/jiaozifs/jiaozifs/utils" "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/jiaozifs/jiaozifs/utils/httputil" @@ -30,6 +32,7 @@ var objLog = logging.Logger("object_ctl") type ObjectController struct { fx.In + BaseController PublicStorageConfig params.AdapterConfig Repo models.IRepo @@ -48,17 +51,21 @@ func (oct ObjectController) DeleteObject(ctx context.Context, w *api.JiaozifsRes return } - if operator.Name != ownerName { //todo check permission - w.Forbidden() - return - } - repository, err := oct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) if err != nil { w.Error(err) return } + if !oct.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.DeleteObjectAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { + return + } + ref, err := oct.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(params.RefName)) if err != nil { w.Error(err) @@ -109,18 +116,22 @@ func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsRespon return } - if operator.Name != ownerName { //todo check permission - w.Forbidden() + repository, err := oct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) + if err != nil { + w.Error(err) return } - repoModel, err := oct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) - if err != nil { - w.Error(err) + if !oct.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ReadObjectAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { return } - workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repoModel, oct.Repo, oct.PublicStorageConfig) + workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repository, oct.Repo, oct.PublicStorageConfig) if err != nil { w.Error(err) return @@ -203,18 +214,22 @@ func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsRespo return } - if operator.Name != ownerName { //todo check permission - w.Forbidden() + repository, err := oct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) + if err != nil { + w.Error(err) return } - repoModel, err := oct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) - if err != nil { - w.Error(err) + if !oct.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ReadObjectAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { return } - workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repoModel, oct.Repo, oct.PublicStorageConfig) + workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repository, oct.Repo, oct.PublicStorageConfig) if err != nil { w.Error(err) return @@ -269,6 +284,27 @@ func (oct ObjectController) HeadObject(ctx context.Context, w *api.JiaozifsRespo } func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsResponse, r *http.Request, ownerName string, repositoryName string, params api.UploadObjectParams) { //nolint + owner, err := oct.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + repository, err := oct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) + if err != nil { + w.Error(err) + return + } + + if !oct.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ReadObjectAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { + return + } + // read request body parse multipart for "content" and upload the data contentType := r.Header.Get("Content-Type") mediaType, p, err := mime.ParseMediaType(contentType) @@ -326,24 +362,7 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes return } - owner, err := oct.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) - if err != nil { - w.Error(err) - return - } - - if operator.Name != ownerName { //todo check permission - w.Forbidden() - return - } - - repoModel, err := oct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) - if err != nil { - w.Error(err) - return - } - - workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repoModel, oct.Repo, oct.PublicStorageConfig) + workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repository, oct.Repo, oct.PublicStorageConfig) if err != nil { w.Error(err) return diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index 0f51582d..05bde41f 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -8,7 +8,9 @@ import ( "net/http" "time" + "github.com/jiaozifs/jiaozifs/auth/rbac" "github.com/jiaozifs/jiaozifs/controller/validator" + "github.com/jiaozifs/jiaozifs/models/rbacmodel" "github.com/google/uuid" logging "github.com/ipfs/go-log/v2" @@ -30,6 +32,7 @@ var repoLog = logging.Logger("repo control") type RepositoryController struct { fx.In + BaseController Repo models.IRepo PublicStorageConfig params.AdapterConfig @@ -42,6 +45,15 @@ func (repositoryCtl RepositoryController) ListRepositoryOfAuthenticatedUser(ctx return } + if !repositoryCtl.authorize(ctx, w, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ListRepositoriesAction, + Resource: rbacmodel.RepoUArn(operator.ID.String()), + }, + }) { + return + } + listRepoParams := models.NewListRepoParams() if params.Prefix != nil && len(*params.Prefix) > 0 { listRepoParams.SetName(*params.Prefix, models.PrefixMatch) @@ -86,13 +98,13 @@ func (repositoryCtl RepositoryController) ListRepository(ctx context.Context, w return } - operator, err := auth.GetOperator(ctx) - if err != nil { - w.Error(err) - return - } - if owner.ID != operator.ID { //todo check public or private and allow access public repos - w.Forbidden() + //TODO should get (private repo repositories has been granted) and (public repositories) + if !repositoryCtl.authorize(ctx, w, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ListRepositoriesAction, + Resource: rbacmodel.RepoUArn(owner.ID.String()), + }, + }) { return } @@ -145,6 +157,15 @@ func (repositoryCtl RepositoryController) CreateRepository(ctx context.Context, return } + if !repositoryCtl.authorize(ctx, w, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.CreateRepositoryAction, + Resource: rbacmodel.RepoUArn(operator.ID.String()), + }, + }) { + return + } + var usePublicStorage = true storageConfig := utils.StringValue(body.BlockstoreConfig) repoID := uuid.New() @@ -209,26 +230,24 @@ func (repositoryCtl RepositoryController) CreateRepository(ctx context.Context, } func (repositoryCtl RepositoryController) DeleteRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.DeleteRepositoryParams) { - operator, err := auth.GetOperator(ctx) + owner, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) if err != nil { w.Error(err) return } - owner, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + repository, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) if err != nil { w.Error(err) return } - if operator.Name != owner.Name { - w.Forbidden() - return - } - - repository, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) - if err != nil { - w.Error(err) + if !repositoryCtl.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.DeleteRepositoryAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { return } @@ -260,6 +279,7 @@ func (repositoryCtl RepositoryController) DeleteRepository(ctx context.Context, if err != nil { return err } + // delete tree _, err = repositoryCtl.Repo.FileTreeRepo(repository.ID).Delete(ctx, models.NewDeleteTreeParams()) if err != nil { @@ -268,6 +288,12 @@ func (repositoryCtl RepositoryController) DeleteRepository(ctx context.Context, //delete wip _, err = repositoryCtl.Repo.WipRepo().Delete(ctx, models.NewDeleteWipParams().SetRepositoryID(repository.ID)) + if err != nil { + return err + } + + //delete all membership + _, err = repositoryCtl.Repo.MemberRepo().DeleteMember(ctx, models.NewDeleteMemberParams().SetRepoID(repository.ID)) return err }) if err != nil { @@ -310,26 +336,24 @@ func (repositoryCtl RepositoryController) DeleteRepository(ctx context.Context, } func (repositoryCtl RepositoryController) GetRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string) { - operator, err := auth.GetOperator(ctx) + owner, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) if err != nil { w.Error(err) return } - owner, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + repo, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) if err != nil { w.Error(err) return } - if operator.Name != owner.Name { //todo check public or private / and permission - w.Forbidden() - return - } - - repo, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) - if err != nil { - w.Error(err) + if !repositoryCtl.authorizeMember(ctx, w, repo.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ReadRepositoryAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repo.ID.String()), + }, + }) { return } @@ -337,26 +361,24 @@ func (repositoryCtl RepositoryController) GetRepository(ctx context.Context, w * } func (repositoryCtl RepositoryController) UpdateRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.UpdateRepositoryJSONRequestBody, ownerName string, repositoryName string) { - operator, err := auth.GetOperator(ctx) + owner, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) if err != nil { w.Error(err) return } - owner, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + repo, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) if err != nil { w.Error(err) return } - if operator.Name != ownerName { //todo check permission to modify owner repo - w.Forbidden() - return - } - - repo, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) - if err != nil { - w.Error(err) + if !repositoryCtl.authorizeMember(ctx, w, repo.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.UpdateRepositoryAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repo.ID.String()), + }, + }) { return } @@ -384,26 +406,24 @@ func (repositoryCtl RepositoryController) UpdateRepository(ctx context.Context, } func (repositoryCtl RepositoryController) GetCommitsInRef(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.GetCommitsInRefParams) { - operator, err := auth.GetOperator(ctx) + owner, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) if err != nil { w.Error(err) return } - owner, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + repository, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) if err != nil { w.Error(err) return } - if operator.Name != ownerName { //todo check public or private - w.Forbidden() - return - } - - repository, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) - if err != nil { - w.Error(err) + if !repositoryCtl.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ReadCommitAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { return } diff --git a/controller/user_ctl.go b/controller/user_ctl.go index 23ba9a19..9859538e 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -3,11 +3,13 @@ package controller import ( "context" "encoding/hex" + "fmt" "net/http" "time" + "github.com/jiaozifs/jiaozifs/auth/rbac" "github.com/jiaozifs/jiaozifs/controller/validator" - + "github.com/jiaozifs/jiaozifs/models/rbacmodel" "github.com/jiaozifs/jiaozifs/utils" "github.com/go-openapi/swag" @@ -23,12 +25,9 @@ import ( var userCtlLog = logging.Logger("user_ctl") -const ( - AuthHeader = "Authorization" -) - type UserController struct { fx.In + BaseController SessionStore sessions.Store Repo models.IRepo @@ -94,20 +93,68 @@ func (userCtl UserController) Register(ctx context.Context, w *api.JiaozifsRespo w.BadRequest(err.Error()) return } + // check username, email + count1, err := userCtl.Repo.UserRepo().Count(ctx, models.NewCountUserParam().SetName(body.Name)) + if err != nil { + w.Error(err) + return + } + count2, err := userCtl.Repo.UserRepo().Count(ctx, models.NewCountUserParam().SetEmail(string(body.Email))) + if err != nil { + w.Error(err) + return + } - register := auth.Register{ - Username: body.Name, - Email: string(body.Email), - Password: body.Password, + if count1+count2 > 0 { + w.BadRequest(fmt.Sprintf("username %s or email %s not found ", body.Name, body.Email)) } - // perform register - err = register.Register(ctx, userCtl.Repo.UserRepo()) + // reserve temporarily + password, err := auth.HashPassword(body.Password) if err != nil { w.Error(err) return } - w.OK() + + // insert db + user := &models.User{ + Name: body.Name, + Email: string(body.Email), + EncryptedPassword: string(password), + CurrentSignInAt: time.Time{}, + LastSignInAt: time.Time{}, + CurrentSignInIP: "", + LastSignInIP: "", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + var insertUser *models.User + err = userCtl.Repo.Transaction(ctx, func(repo models.IRepo) error { + insertUser, err = repo.UserRepo().Insert(ctx, user) + if err != nil { + return fmt.Errorf("inser user %s user error %w", body.Name, err) + } + + userOwnGroup, err := repo.GroupRepo().Get(ctx, rbacmodel.NewGetGroupParams().SetName(rbac.UserOwnAccess)) + if err != nil { + return err + } + //bind own user group + _, err = repo.UserGroupRepo().Insert(ctx, &rbacmodel.UserGroup{ + UserID: insertUser.ID, + GroupID: userOwnGroup.ID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + return err + }) + if err != nil { + w.Error(err) + return + } + + w.JSON(userInfoToDto(insertUser), http.StatusCreated) } func (userCtl UserController) GetUserInfo(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request) { @@ -118,26 +165,26 @@ func (userCtl UserController) GetUserInfo(ctx context.Context, w *api.JiaozifsRe return } - // perform GetUserInfo - userInfo := api.UserInfo{ - Name: user.Name, - Email: openapitypes.Email(user.Email), - CurrentSignInAt: utils.Int64(user.CurrentSignInAt.UnixMilli()), - CurrentSignInIp: &user.CurrentSignInIP, - LastSignInAt: utils.Int64(user.LastSignInAt.UnixMilli()), - LastSignInIp: &user.LastSignInIP, - UpdatedAt: user.UpdatedAt.UnixMilli(), - CreatedAt: user.CreatedAt.UnixMilli(), + if !userCtl.authorize(ctx, w, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ListRepositoriesAction, + Resource: rbacmodel.RepoUArn(user.ID.String()), + }, + }) { + return } - w.JSON(userInfo) + + w.JSON(userInfoToDto(user)) } func (userCtl UserController) Logout(_ context.Context, w *api.JiaozifsResponse, r *http.Request) { + //todo only web credencial could logout session, err := userCtl.SessionStore.Get(r, auth.InternalAuthSessionName) if err != nil { w.Error(err) return } + session.Options.MaxAge = -1 if session.Save(r, w) != nil { userCtlLog.Errorf("Failed to save internal auth session %v", err) @@ -146,3 +193,17 @@ func (userCtl UserController) Logout(_ context.Context, w *api.JiaozifsResponse, } http.Redirect(w, r, "/", http.StatusTemporaryRedirect) } + +func userInfoToDto(user *models.User) *api.UserInfo { + return &api.UserInfo{ + Id: user.ID, + Name: user.Name, + Email: openapitypes.Email(user.Email), + CurrentSignInAt: utils.Int64(user.CurrentSignInAt.UnixMilli()), + CurrentSignInIp: &user.CurrentSignInIP, + LastSignInAt: utils.Int64(user.LastSignInAt.UnixMilli()), + LastSignInIp: &user.LastSignInIP, + UpdatedAt: user.UpdatedAt.UnixMilli(), + CreatedAt: user.CreatedAt.UnixMilli(), + } +} diff --git a/controller/wip_ctl.go b/controller/wip_ctl.go index 0995d253..375cfac6 100644 --- a/controller/wip_ctl.go +++ b/controller/wip_ctl.go @@ -8,8 +8,10 @@ import ( "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/auth" + "github.com/jiaozifs/jiaozifs/auth/rbac" "github.com/jiaozifs/jiaozifs/block/params" "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/models/rbacmodel" "github.com/jiaozifs/jiaozifs/utils" "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/jiaozifs/jiaozifs/versionmgr" @@ -18,6 +20,7 @@ import ( type WipController struct { fx.In + BaseController Repo models.IRepo PublicStorageConfig params.AdapterConfig @@ -43,8 +46,23 @@ func (wipCtl WipController) GetWip(ctx context.Context, w *api.JiaozifsResponse, return } - if operator.Name != owner.Name { //todo check permission to operator ownerRepo - w.Forbidden() + if !wipCtl.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Type: rbac.NodeTypeAnd, + Nodes: []rbac.Node{ + { + Permission: rbac.Permission{ + Action: rbacmodel.ReadWipAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }, + { + Permission: rbac.Permission{ + Action: rbacmodel.CreateWipAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }, + }, + }) { return } @@ -91,8 +109,12 @@ func (wipCtl WipController) ListWip(ctx context.Context, w *api.JiaozifsResponse return } - if operator.Name != owner.Name { //todo check permission to operator ownerRepo - w.Forbidden() + if !wipCtl.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ListWipAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { return } @@ -129,8 +151,29 @@ func (wipCtl WipController) CommitWip(ctx context.Context, w *api.JiaozifsRespon return } - if operator.Name != owner.Name { //todo check permission to operator ownerRepo - w.Forbidden() + if !wipCtl.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Type: rbac.NodeTypeAnd, + Nodes: []rbac.Node{ + { + Permission: rbac.Permission{ + Action: rbacmodel.ReadWipAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }, + { + Permission: rbac.Permission{ + Action: rbacmodel.WriteBranchAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }, + { + Permission: rbac.Permission{ + Action: rbacmodel.ReadCommitAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }, + }, + }) { return } @@ -174,8 +217,12 @@ func (wipCtl WipController) UpdateWip(ctx context.Context, w *api.JiaozifsRespon return } - if operator.Name != owner.Name { //todo check permission to operator ownerRepo - w.Forbidden() + if !wipCtl.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.WriteWipAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { return } @@ -253,8 +300,12 @@ func (wipCtl WipController) DeleteWip(ctx context.Context, w *api.JiaozifsRespon return } - if operator.Name != owner.Name { //todo check permission to operator ownerRepo - w.Forbidden() + if !wipCtl.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.DeleteBranchAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { return } @@ -298,8 +349,29 @@ func (wipCtl WipController) GetWipChanges(ctx context.Context, w *api.JiaozifsRe return } - if operator.Name != owner.Name { //todo check permission to operator ownerRepo - w.Forbidden() + if !wipCtl.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Type: rbac.NodeTypeAnd, + Nodes: []rbac.Node{ + { + Permission: rbac.Permission{ + Action: rbacmodel.ReadCommitAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }, + { + Permission: rbac.Permission{ + Action: rbacmodel.ReadWipAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }, + { + Permission: rbac.Permission{ + Action: rbacmodel.ReadBranchAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }, + }, + }) { return } diff --git a/docs/authorization.md b/docs/authorization.md new file mode 100644 index 00000000..f2678dd6 --- /dev/null +++ b/docs/authorization.md @@ -0,0 +1,37 @@ +# Authorization + +jiaozifs支持 basic_auth, cookie_auth, jwt_token, aksk四种认证方式 + +# basic_auth + +采用基础的用户名密码认证方式, 在header中加入Authorization头用来传送用户名密码,用户名密码部分的形式是base64(username:password), +基础授权用户名密码几乎是明文传送的,因此使用的时候确保传输环境是安全的 + +```go +Authorization:Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== +``` + +# cookie_auth + +cookie认证方式通常用于网页开发中,用户调用登录接口后会返回一个cookie内容,用户需要再以后得请求中携带这个cookie,试试上由于跨域问题用的相对较少 + +```go +internal_auth_session:MTcwOTUyNjgzM3xEWDhFQVFMX2dBQUJFQUVRQUFELUFRSF9nQUFCQm5OMGNtbHVad3dIQUFWMGIydGxiZ1p6ZEhKcGJtY01fLU1BXy1CbGVVcG9Za2RqYVU5cFNrbFZla2t4VG1sSmMwbHVValZqUTBrMlNXdHdXRlpEU2prdVpYbEthR1JYVVdsUGFVcHpZakprY0dKcFNYTkpiVlkwWTBOSk5rMVVZM2RQVkZWNlRVUlJlVTVEZDJsaFYwWXdTV3B2ZUU1NlFUVk9WRWt5VDBSSk1FeERTbkJhUTBrMlNXcHJlVTlFVFRWT1ZGbDZURlJyTkUxVVkzUk9SRkV6VFhrd05FMXRXbTFNVkd4cVRsZEdhRnBIVlRCUFJGbDNUMU5KYzBsdVRqRlphVWsyU1cxd2NHSlhNVFZKYmpBdVpscFdYMDlITFdnM1NHOHlXamhTYkVsTFdFNVNTbGsyVEVjMFowUjBSM0JKZG5wRlVXWnRRalp0UlE9PXxUDjorrE1rTgIEFNBfPr-1EdctnN7NxpOJBuSgyEZXvg== +``` + +# jwt_token + +token认证方式现在用于前后端分离的前端页面开发过程,用户调用登录接口后会返回一个包含token信息的结构体,用户在后续请求中需要携带这个token信息 + +```go +Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJsb2dpbiIsImV4cCI6MTcwOTUzMDQyNCwiaWF0IjoxNzA5NTI2ODI0LCJpZCI6IjkyODM5NTYzLTk4MTctNDQ3My04MmZmLTljNWFhZGU0ODYwOSIsInN1YiI6ImppbW15In0.fZV_OG-h7Ho2Z8RlIKXNRJY6LG4gDtGpIvzEQfmB6mE +``` + +## aksk认证 + +aksk认证常用于接口开发,在jiaozifs中需要调用创建aksk的接口获取accesskey和secretkey,目前aksk还不支持权限配置和生命周期设置,日后会增强这一块的功能 + +```go +access_key:02c7d19c15a712779cacaf54bd24499d +secret_key:dc05e65dfafe987ff26e66ffb7036684 +``` diff --git a/docs/jiaozifs_aim.drawio b/docs/draws/jiaozifs_aim.drawio similarity index 100% rename from docs/jiaozifs_aim.drawio rename to docs/draws/jiaozifs_aim.drawio diff --git a/docs/jiaozifs_mind.drawio b/docs/draws/jiaozifs_mind.drawio similarity index 100% rename from docs/jiaozifs_mind.drawio rename to docs/draws/jiaozifs_mind.drawio diff --git a/docs/draws/rbac_role.drawio b/docs/draws/rbac_role.drawio new file mode 100644 index 00000000..e0ca16a9 --- /dev/null +++ b/docs/draws/rbac_role.drawio @@ -0,0 +1,308 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/rbac.md b/docs/rbac.md new file mode 100644 index 00000000..c99e01dd --- /dev/null +++ b/docs/rbac.md @@ -0,0 +1,166 @@ +# rbac设计方案 + +rbac中引入resource_type, resource, statement, policy, group,user,的概念他们之间的关系如下图 + +![image](https://github.com/jiaozifs/jiaozifs/assets/41407352/632d8b90-25d4-423e-bcea-5114c339ddf8) + +## 类型组织 +整体上对于权限的组织情况如下 +- resource_type: 资源类型 +- resource: 资源种类 +- statement: 一组预定义的权限集合 +- poly: 结合了资源的权限 +- group: 用户可直接理解的权限组 +- user: 用户 + +### 资源类型 + +- repo 用户仓库相关功能 +- user 用户相关接口 +- auth 授权相关功能 + +### action + +action 列表 +```go +var Actions = []string{ +"repo:ReadRepository", +"repo:CreateRepository", +"repo:UpdateRepository", +"repo:DeleteRepository", +"repo:ListRepositories", +"repo:ReadObject", +"repo:WriteObject", +"repo:DeleteObject", +"repo:ListObjects", +"repo:CreateCommit", +"repo:ReadCommit", +"repo:ListCommits", +"repo:CreateBranch", +"repo:DeleteBranch", +"repo:ReadBranch", +"repo:ReadBranch", +"repo:ListBranches", +"repo:GetWip", +"repo:ListWip", +"repo:WriteWip", +"repo:CreateWip", +"repo:DeleteWip", +"repo:ReadConfig", +"repo:WriteConfig", +"repo:CreateMergeRequest", +"repo:ReadMergeRequest", +"repo:UpdateMergeRequest", +"repo:ListMergeRequest", +"repo:MergeMergeRequest", +"repo:AddGroupMember", +"repo:RemoveGroupMember", +"repo:GetGroupMember", +"repo:GetGroupMember", +"auth:ReadGroup", +"auth:CreateGroup", +"auth:DeleteGroup", +"auth:ListGroups", +"auth:ReadPolicy", +"auth:CreatePolicy", +"auth:UpdatePolicy", +"auth:DeletePolicy", +"auth:ListPolicies", +"auth:AttachPolicy", +"auth:DetachPolicy", +"user:UserProfile", +"user:ReadUser", +"user:ListUsers", +"user:DeleteUser", +"user:ReadCredentials", +"user:CreateCredentials", +"user:DeleteCredentials", +"user:ListCredentials", +} +``` + +### policy + +- FSFullAccess 全访问权限 +- RepoRead 仓库读取权限 +- RepoReadWrite 仓库读写权限 +- RepoConfig 仓库配置权限 +- UserAccess 用户配置自己信息的权限 + +### group + +- SuperUsers 超级用户组 +- RepoAdmins 仓库管理权限 +- RepoWrite 写入权限 +- RepoRead 读取权限 + + +## 表设计 +数据库设计基于postgres + +策略表 +```go +type Statement struct { + Effect string `json:"effect"` + Action []string `json:"action"` + Resource Resource `json:"resource"` +} + +type Policy struct { + bun.BaseModel `bun:"table:policies"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()" json:"id"` + // Name policy name + Name string `bun:"name,unique,notnull" json:"name"` + // Actions + Statements []Statement `bun:"statements,type:jsonb,notnull" json:"statements"` + // CreatedAt + CreatedAt time.Time `bun:"created_at,type:timestamp,notnull" json:"created_at"` + // UpdatedAt + UpdatedAt time.Time `bun:"updated_at,type:timestamp,notnull" json:"updated_at"` +} +``` + +group 表 +```go +type Group struct { + bun.BaseModel `bun:"table:groups"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()" json:"id"` + // Name policy name + Name string `bun:"name,unique,notnull" json:"secret_key"` + // Policies + Policies []uuid.UUID `bun:"policies,type:jsonb,notnull" json:"policies"` + // CreatedAt + CreatedAt time.Time `bun:"created_at,type:timestamp,notnull" json:"created_at"` + // UpdatedAt + UpdatedAt time.Time `bun:"updated_at,type:timestamp,notnull" json:"updated_at"` +} +``` + +用户组表 +```go +type UserGroup struct { + bun.BaseModel `bun:"table:usergroup"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()" json:"id"` + UserID uuid.UUID `bun:"user_id,type:uuid,unique:user_group_pk,notnull" json:"user_id"` + GroupID uuid.UUID `bun:"group_id,type:uuid,unique:user_group_pk,notnull" json:"group_id"` + // CreatedAt + CreatedAt time.Time `bun:"created_at,type:timestamp,notnull" json:"created_at"` + // UpdatedAt + UpdatedAt time.Time `bun:"updated_at,type:timestamp,notnull" json:"updated_at"` +} +``` + +仓库成员表 +```go +type Member struct { + bun.BaseModel `bun:"table:members"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()" json:"id"` + UserID uuid.UUID `bun:"user_id,type:uuid,unique:user_repo_pk,notnull" json:"user_id"` + RepoID uuid.UUID `bun:"repo_id,type:uuid,unique:user_repo_pk,notnull" json:"repo_id"` + GroupID uuid.UUID `bun:"group_id,type:uuid,notnull" json:"group_id"` + // CreatedAt + CreatedAt time.Time `bun:"created_at,type:timestamp,notnull" json:"created_at"` + // UpdatedAt + UpdatedAt time.Time `bun:"updated_at,type:timestamp,notnull" json:"updated_at"` +} +``` diff --git a/go.mod b/go.mod index 96bda25b..d1d5b8a0 100644 --- a/go.mod +++ b/go.mod @@ -177,6 +177,7 @@ require ( github.com/libp2p/go-libp2p-routing-helpers v0.7.3 // indirect github.com/libp2p/go-msgio v0.3.0 // indirect github.com/libp2p/go-netroute v0.2.1 // indirect + github.com/m1/go-generate-password v0.2.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect diff --git a/go.sum b/go.sum index 082a8553..5f3e90e7 100644 --- a/go.sum +++ b/go.sum @@ -565,6 +565,8 @@ github.com/libp2p/go-yamux/v4 v4.0.1 h1:FfDR4S1wj6Bw2Pqbc8Uz7pCxeRBPbwsBbEdfwiCy github.com/libp2p/go-yamux/v4 v4.0.1/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4= github.com/libp2p/zeroconf/v2 v2.2.0 h1:Cup06Jv6u81HLhIj1KasuNM/RHHrJ8T7wOTS4+Tv53Q= github.com/libp2p/zeroconf/v2 v2.2.0/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0DmyYyNO1Xs= +github.com/m1/go-generate-password v0.2.0 h1:T4IJy8tzv9Svjn7obm+tNmfcWd31WYeTgNxH2BhliyM= +github.com/m1/go-generate-password v0.2.0/go.mod h1:QLABVln3jsxIksMUjRv4UXi6f+1cQ3rnfj28nADpCgk= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= diff --git a/integrationtest/aksk_test.go b/integrationtest/aksk_test.go index 29443cea..f8aeece7 100644 --- a/integrationtest/aksk_test.go +++ b/integrationtest/aksk_test.go @@ -16,8 +16,11 @@ func AkSkSpec(ctx context.Context, urlStr string) func(c convey.C) { var aksk *api.Aksk return func(c convey.C) { userName := "muly" - createUser(ctx, c, client, userName) - loginAndSwitch(ctx, c, client, "muly login", userName, false) + + c.Convey("init", func(c convey.C) { + createUser(ctx, client, userName) + loginAndSwitch(ctx, client, userName, false) + }) c.Convey("create aksk", func(c convey.C) { c.Convey("no auth", func() { @@ -180,16 +183,3 @@ func AkSkSpec(ctx context.Context, urlStr string) func(c convey.C) { }) } } - -func createAksk(ctx context.Context, client *api.Client) (*api.Aksk, error) { - resp, err := client.CreateAksk(ctx, &api.CreateAkskParams{Description: utils.String("create ak sk")}) - if err != nil { - return nil, err - } - - akskResult, err := api.ParseCreateAkskResponse(resp) - if err != nil { - return nil, err - } - return akskResult.JSON201, nil -} diff --git a/integrationtest/branch_test.go b/integrationtest/branch_test.go index b01eea29..d9f6b0da 100644 --- a/integrationtest/branch_test.go +++ b/integrationtest/branch_test.go @@ -23,10 +23,12 @@ func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { newAmount := 0 prefix := "feat/" - createUser(ctx, c, client, userName) - loginAndSwitch(ctx, c, client, "mike login", userName, false) - createRepo(ctx, c, client, repoName) + c.Convey("init", func(c convey.C) { + createUser(ctx, client, userName) + loginAndSwitch(ctx, client, userName, false) + createRepo(ctx, client, repoName) + }) c.Convey("create branch", func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors @@ -91,7 +93,7 @@ func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { Source: "main", }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) }) @@ -148,13 +150,15 @@ func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { RefName: "main", }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) }) - createBranch(ctx, c, client, "create sec branch", userName, repoName, "main", "feat/sec_branch") - c.Convey("list branch", func(c convey.C) { + c.Convey("create second branch", func() { + createBranch(ctx, client, userName, repoName, "main", "feat/sec_branch") + }) + c.Convey("no auth", func() { re := client.RequestEditors client.RequestEditors = nil @@ -227,7 +231,7 @@ func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to list branches in others repo", func() { resp, err := client.ListBranches(ctx, "jimmy", "happygo", &api.ListBranchesParams{}) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) }) @@ -255,15 +259,16 @@ func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("delete branch in other's repo", func() { resp, err := client.DeleteBranch(ctx, "jimmy", "happygo", &api.DeleteBranchParams{RefName: "main"}) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("fail to delete default branch", func() { resp, err := client.DeleteBranch(ctx, userName, repoName, &api.DeleteBranchParams{RefName: "main"}) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) }) - createWip(ctx, c, client, "creat wip for delete", userName, repoName, "feat/sec_branch") + c.Convey("delete branch successful", func() { + createWip(ctx, client, userName, repoName, "feat/sec_branch") resp, err := client.DeleteBranch(ctx, userName, repoName, &api.DeleteBranchParams{RefName: "feat/sec_branch"}) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) diff --git a/integrationtest/commit_test.go b/integrationtest/commit_test.go index 3860bb3b..2d9a6bb8 100644 --- a/integrationtest/commit_test.go +++ b/integrationtest/commit_test.go @@ -2,7 +2,6 @@ package integrationtest import ( "context" - "fmt" "net/http" "github.com/jiaozifs/jiaozifs/utils/hash" @@ -21,14 +20,16 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { repoName := "black" branchName := "feat/get_entries_test" - createUser(ctx, c, client, userName) - loginAndSwitch(ctx, c, client, "kitty login", userName, false) - createRepo(ctx, c, client, repoName) - createBranch(ctx, c, client, "create branch", userName, repoName, "main", branchName) - createWip(ctx, c, client, "feat get entries test0", userName, repoName, branchName) - uploadObject(ctx, c, client, "update f1 to test branch", userName, repoName, branchName, "m.dat") - uploadObject(ctx, c, client, "update f2 to test branch", userName, repoName, branchName, "g/x.dat") - uploadObject(ctx, c, client, "update f3 to test branch", userName, repoName, branchName, "g/m.dat") + c.Convey("init", func(c convey.C) { + createUser(ctx, client, userName) + loginAndSwitch(ctx, client, userName, false) + createRepo(ctx, client, repoName) + createBranch(ctx, client, userName, repoName, "main", branchName) + createWip(ctx, client, userName, repoName, branchName) + uploadObject(ctx, client, userName, repoName, branchName, "m.dat") + uploadObject(ctx, client, userName, repoName, branchName, "g/x.dat") + uploadObject(ctx, client, userName, repoName, branchName, "g/m.dat") + }) c.Convey("get wip entries", func(c convey.C) { c.Convey("no auth", func() { @@ -81,7 +82,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("not exit path", func() { @@ -126,7 +127,9 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { }) }) - commitWip(ctx, c, client, "commit kitty first changes", userName, repoName, branchName, "test") + c.Convey("commit kitty first changes", func(c convey.C) { + commitWip(ctx, client, userName, repoName, branchName, "test") + }) c.Convey("get branch entries", func(c convey.C) { c.Convey("no auth", func() { @@ -179,7 +182,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { Type: api.RefTypeBranch, }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("not exit path", func() { @@ -225,10 +228,12 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { }) }) - createWip(ctx, c, client, "main wip", userName, repoName, "main") - uploadObject(ctx, c, client, "update f1 to main branch", userName, repoName, "main", "a.dat") //delete\ - uploadObject(ctx, c, client, "update f2 to main branch", userName, repoName, "main", "g/m.dat") //modify - commitWip(ctx, c, client, "commit branch change", userName, repoName, "main", "test") + c.Convey("prepare data for commit test", func(c convey.C) { + createWip(ctx, client, userName, repoName, "main") + uploadObject(ctx, client, userName, repoName, "main", "a.dat") //delete\ + uploadObject(ctx, client, userName, repoName, "main", "g/m.dat") //modify + commitWip(ctx, client, userName, repoName, "main", "test") + }) c.Convey("get commit entries", func(c convey.C) { c.Convey("fail to get entries in uncorrected hash", func() { @@ -338,7 +343,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { Path: utils.String("/"), }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("not exit path", func() { @@ -379,27 +384,29 @@ func GetCommitChangesSpec(ctx context.Context, urlStr string) func(c convey.C) { userName := "kelly" repoName := "gcc" - createUser(ctx, c, client, userName) - loginAndSwitch(ctx, c, client, "getCommitChanges login", userName, false) - createRepo(ctx, c, client, repoName) - createWip(ctx, c, client, "feat get entries test0", userName, repoName, "main") - uploadObject(ctx, c, client, "update m.dat to test branch", userName, repoName, "main", "m.dat") - commitWip(ctx, c, client, "commit kelly first changes", userName, repoName, "main", "test") + c.Convey("init", func(c convey.C) { + createUser(ctx, client, userName) + loginAndSwitch(ctx, client, userName, false) + createRepo(ctx, client, repoName) + createWip(ctx, client, userName, repoName, "main") + uploadObject(ctx, client, userName, repoName, "main", "m.dat") + commitWip(ctx, client, userName, repoName, "main", "test") - uploadObject(ctx, c, client, "update g/x.dat to test branch", userName, repoName, "main", "g/x.dat") - commitWip(ctx, c, client, "commit kelly second changes", userName, repoName, "main", "test") + uploadObject(ctx, client, userName, repoName, "main", "g/x.dat") + commitWip(ctx, client, userName, repoName, "main", "test") - //delete - deleteObject(ctx, c, client, "delete g/x.dat", userName, repoName, "main", "g/x.dat") + //delete + deleteObject(ctx, client, userName, repoName, "main", "g/x.dat") - //modify - deleteObject(ctx, c, client, "delete m.dat", userName, repoName, "main", "m.dat") - uploadObject(ctx, c, client, "update m.dat to test branch again", userName, repoName, "main", "m.dat") + //modify + deleteObject(ctx, client, userName, repoName, "main", "m.dat") + uploadObject(ctx, client, userName, repoName, "main", "m.dat") - //insert - uploadObject(ctx, c, client, "update g/m.dat to test branch", userName, repoName, "main", "g/m.dat") - commitWip(ctx, c, client, "commit kelly third changes", userName, repoName, "main", "test") + //insert + uploadObject(ctx, client, userName, repoName, "main", "g/m.dat") + commitWip(ctx, client, userName, repoName, "main", "test") + }) c.Convey("get commit change", func(c convey.C) { c.Convey("list commit history", func() { resp, err := client.GetCommitsInRef(ctx, userName, repoName, &api.GetCommitsInRefParams{RefName: utils.String("main")}) @@ -443,7 +450,7 @@ func GetCommitChangesSpec(ctx context.Context, urlStr string) func(c convey.C) { Path: utils.String("/"), }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("not exit path", func() { @@ -455,9 +462,6 @@ func GetCommitChangesSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("success to get first changes", func() { - fmt.Println(commits[0].Hash) - fmt.Println(commits[1].Hash) - fmt.Println(commits[2].Hash) resp, err := client.GetCommitChanges(ctx, userName, repoName, commits[0].Hash, &api.GetCommitChangesParams{ Path: utils.String("/"), }) diff --git a/integrationtest/group_test.go b/integrationtest/group_test.go new file mode 100644 index 00000000..cbc82e73 --- /dev/null +++ b/integrationtest/group_test.go @@ -0,0 +1,25 @@ +package integrationtest + +import ( + "context" + + "github.com/jiaozifs/jiaozifs/api" + apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" + "github.com/smartystreets/goconvey/convey" +) + +func GroupSpec(ctx context.Context, urlStr string) func(c convey.C) { + client, _ := api.NewClient(urlStr + apiimpl.APIV1Prefix) + return func(c convey.C) { + userName := "grouptest" + createUser(ctx, client, userName) + loginAndSwitch(ctx, client, userName, false) + + resp, err := client.ListRepoGroup(ctx) + convey.ShouldBeNil(c, err) + + result, err := api.ParseListRepoGroupResponse(resp) + convey.ShouldBeNil(c, err) + convey.ShouldHaveLength(result, 3) + } +} diff --git a/integrationtest/helper_test.go b/integrationtest/helper_test.go index 33d75802..4c527c92 100644 --- a/integrationtest/helper_test.go +++ b/integrationtest/helper_test.go @@ -10,14 +10,16 @@ import ( "net/http" "os" "strings" + "sync/atomic" "testing" "time" + openapi_types "github.com/oapi-codegen/runtime/types" + "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/cmd" "github.com/jiaozifs/jiaozifs/testhelper" "github.com/jiaozifs/jiaozifs/utils" - openapi_types "github.com/oapi-codegen/runtime/types" "github.com/phayes/freeport" "github.com/smartystreets/goconvey/convey" "github.com/stretchr/testify/require" @@ -41,11 +43,14 @@ func Daemon(ctx context.Context, writer io.Writer, jzHome string, listen string) } func TestDoubleInit(t *testing.T) { //nolint - url := "http://127.0.0.1:1234" ctx := context.Background() + closeDB, connectString, _ := testhelper.SetupDatabase(ctx, t) + defer closeDB() + + url := "http://127.0.0.1:1234" tmpDir, err := os.MkdirTemp(os.TempDir(), "*") require.NoError(t, err) - require.NoError(t, InitCmd(ctx, tmpDir, url, "")) + require.NoError(t, InitCmd(ctx, tmpDir, url, connectString)) err = InitCmd(ctx, tmpDir, url, "") require.Error(t, err) require.Contains(t, err.Error(), "config already exit") @@ -76,7 +81,6 @@ func SetupDaemon(t *testing.T, ctx context.Context) (string, Closer) { //nolint require.NoError(t, err) } }() - fmt.Println(connectString) //wai for api ready ticker := time.NewTicker(time.Second) @@ -102,122 +106,114 @@ func SetupDaemon(t *testing.T, ctx context.Context) (string, Closer) { //nolint } } -var count int - -func createUser(ctx context.Context, c convey.C, client *api.Client, userName string) { - c.Convey("register "+userName, func() { - count++ - resp, err := client.Register(ctx, api.RegisterJSONRequestBody{ - Name: userName, - Password: "12345678", - Email: openapi_types.Email(fmt.Sprintf("mock%d@gmail.com", count)), - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) +var count atomic.Int32 + +func createUser(ctx context.Context, client *api.Client, userName string) { + resp, err := client.Register(ctx, api.RegisterJSONRequestBody{ + Name: userName, + Password: "12345678", + Email: openapi_types.Email(fmt.Sprintf("mock%d@gmail.com", count.Add(1))), }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) } -func loginAndSwitch(ctx context.Context, c convey.C, client *api.Client, title, userName string, useCookie bool) { - c.Convey("login "+title, func() { - resp, err := client.Login(ctx, api.LoginJSONRequestBody{ - Name: userName, - Password: "12345678", - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) - loginResult, err := api.ParseLoginResponse(resp) - convey.So(err, convey.ShouldBeNil) - - client.RequestEditors = nil - client.RequestEditors = append(client.RequestEditors, func(ctx context.Context, req *http.Request) error { - if useCookie { - for _, cookie := range resp.Cookies() { - req.AddCookie(cookie) - } - } else { - req.Header.Add("Authorization", "Bearer "+loginResult.JSON200.Token) +func loginAndSwitch(ctx context.Context, client *api.Client, userName string, useCookie bool) { + resp, err := client.Login(ctx, api.LoginJSONRequestBody{ + Name: userName, + Password: "12345678", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + loginResult, err := api.ParseLoginResponse(resp) + convey.So(err, convey.ShouldBeNil) + + client.RequestEditors = nil + client.RequestEditors = append(client.RequestEditors, func(ctx context.Context, req *http.Request) error { + if useCookie { + for _, cookie := range resp.Cookies() { + req.AddCookie(cookie) } - return nil - }) + } else { + req.Header.Add("Authorization", "Bearer "+loginResult.JSON200.Token) + } + return nil }) } -func createBranch(ctx context.Context, c convey.C, client *api.Client, title string, user string, repoName string, source, refName string) { - c.Convey("create branch "+title, func() { - resp, err := client.CreateBranch(ctx, user, repoName, api.CreateBranchJSONRequestBody{ - Source: source, - Name: refName, - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) +func createBranch(ctx context.Context, client *api.Client, user string, repoName string, source, refName string) { + resp, err := client.CreateBranch(ctx, user, repoName, api.CreateBranchJSONRequestBody{ + Source: source, + Name: refName, }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) } -func createRepo(ctx context.Context, c convey.C, client *api.Client, repoName string) { - c.Convey("create repo "+repoName, func() { - resp, err := client.CreateRepository(ctx, api.CreateRepositoryJSONRequestBody{ - Name: repoName, - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) +func createRepo(ctx context.Context, client *api.Client, repoName string) { + resp, err := client.CreateRepository(ctx, api.CreateRepositoryJSONRequestBody{ + Name: repoName, }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) } -func uploadObject(ctx context.Context, c convey.C, client *api.Client, title string, user string, repoName string, refName string, path string) { //nolint - c.Convey("upload object "+title, func(c convey.C) { - resp, err := client.UploadObjectWithBody(ctx, user, repoName, &api.UploadObjectParams{ - RefName: refName, - Path: path, - }, "application/octet-stream", io.LimitReader(rand.Reader, 50)) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) - }) +func uploadObject(ctx context.Context, client *api.Client, user string, repoName string, refName string, path string) { //nolint + resp, err := client.UploadObjectWithBody(ctx, user, repoName, &api.UploadObjectParams{ + RefName: refName, + Path: path, + }, "application/octet-stream", io.LimitReader(rand.Reader, 50)) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) } -func deleteObject(ctx context.Context, c convey.C, client *api.Client, title string, user string, repoName string, refName string, path string) { //nolint - c.Convey("upload object "+title, func(c convey.C) { - c.Convey("success upload object", func() { - resp, err := client.DeleteObject(ctx, user, repoName, &api.DeleteObjectParams{ - RefName: refName, - Path: path, - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) - }) +func deleteObject(ctx context.Context, client *api.Client, user string, repoName string, refName string, path string) { //nolint + resp, err := client.DeleteObject(ctx, user, repoName, &api.DeleteObjectParams{ + RefName: refName, + Path: path, }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) } -func createWip(ctx context.Context, c convey.C, client *api.Client, title string, user string, repoName string, refName string) { - c.Convey("create wip "+title, func() { - resp, err := client.GetWip(ctx, user, repoName, &api.GetWipParams{ - RefName: refName, - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) +func createWip(ctx context.Context, client *api.Client, user string, repoName string, refName string) { + resp, err := client.GetWip(ctx, user, repoName, &api.GetWipParams{ + RefName: refName, }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) } -func commitWip(ctx context.Context, c convey.C, client *api.Client, title string, user string, repoName string, refName string, msg string) { - c.Convey("commit wip "+title, func() { - resp, err := client.CommitWip(ctx, user, repoName, &api.CommitWipParams{ - RefName: refName, - Msg: msg, - }) - - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) +func commitWip(ctx context.Context, client *api.Client, user string, repoName string, refName string, msg string) { + resp, err := client.CommitWip(ctx, user, repoName, &api.CommitWipParams{ + RefName: refName, + Msg: msg, }) + + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) } -func createMergeRequest(ctx context.Context, c convey.C, client *api.Client, title string, user string, repoName string, sourceBranch string, targetBranch string) { - c.Convey("create mr "+title, func() { - resp, err := client.CreateMergeRequest(ctx, user, repoName, api.CreateMergeRequestJSONRequestBody{ - Description: utils.String("create merge request test"), - SourceBranchName: sourceBranch, - TargetBranchName: targetBranch, - Title: "Merge: test", - }) - convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) +func createMergeRequest(ctx context.Context, client *api.Client, user string, repoName string, sourceBranch string, targetBranch string) { + resp, err := client.CreateMergeRequest(ctx, user, repoName, api.CreateMergeRequestJSONRequestBody{ + Description: utils.String("create merge request test"), + SourceBranchName: sourceBranch, + TargetBranchName: targetBranch, + Title: "Merge: test", }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) +} + +func createAksk(ctx context.Context, client *api.Client) (*api.Aksk, error) { + resp, err := client.CreateAksk(ctx, &api.CreateAkskParams{Description: utils.String("create ak sk")}) + if err != nil { + return nil, err + } + + akskResult, err := api.ParseCreateAkskResponse(resp) + if err != nil { + return nil, err + } + return akskResult.JSON201, nil } diff --git a/integrationtest/member_test.go b/integrationtest/member_test.go new file mode 100644 index 00000000..6d4f2236 --- /dev/null +++ b/integrationtest/member_test.go @@ -0,0 +1,385 @@ +package integrationtest + +import ( + "context" + "net/http" + + "github.com/jiaozifs/jiaozifs/auth/rbac" + + "github.com/jiaozifs/jiaozifs/api" + apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" + "github.com/smartystreets/goconvey/convey" +) + +func MemberSpec(ctx context.Context, urlStr string) func(c convey.C) { + client, _ := api.NewClient(urlStr + apiimpl.APIV1Prefix) + + user1Name := "group1test" + testRepoName := "repo1test" + + user2Name := "group2test" + testRepo2Name := "repo2test" + + var user1, user2 *api.UserInfo + var repo1, repo2 *api.Repository + var adminGroup, writeGroup, readGroup *api.Group + + var user1Token, user2Token []api.RequestEditorFn + return func(c convey.C) { + c.Convey("init test", func(c convey.C) { + var err error + createUser(ctx, client, user1Name) + user1Token, err = getToken(ctx, client, user1Name) + convey.ShouldBeNil(c, err) + client.RequestEditors = user1Token + + createRepo(ctx, client, testRepoName) + user1, err = getUser(ctx, client) + convey.ShouldBeNil(c, err) + repo1, err = getRepo(ctx, client, user1Name, testRepoName) + convey.ShouldBeNil(c, err) + + client.RequestEditors = nil + createUser(ctx, client, user2Name) + user2Token, err = getToken(ctx, client, user2Name) + convey.ShouldBeNil(c, err) + client.RequestEditors = user2Token + + createRepo(ctx, client, testRepo2Name) + user2, err = getUser(ctx, client) + convey.ShouldBeNil(c, err) + repo2, err = getRepo(ctx, client, user2Name, testRepo2Name) + convey.ShouldBeNil(c, err) + + readGroup, writeGroup, adminGroup, err = getGroup(ctx, client) + convey.ShouldNotBeNil(adminGroup) + convey.ShouldNotBeNil(writeGroup) + convey.ShouldBeNil(c, err) + }) + + c.Convey("invite member", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.InviteMember(ctx, user2.Name, repo2.Name, &api.InviteMemberParams{ + UserId: user1.Id, + GroupId: readGroup.Id, + }) + client.RequestEditors = re + convey.ShouldBeNil(c, err) + convey.ShouldBeNil(c, resp) + }) + c.Convey("invite self", func() { + resp, err := client.InviteMember(ctx, user2.Name, repo2.Name, &api.InviteMemberParams{ + UserId: user2.Id, + GroupId: readGroup.Id, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + + c.Convey("not exit owner", func() { + resp, err := client.InviteMember(ctx, "fake_owner", repo2.Name, &api.InviteMemberParams{ + UserId: user1.Id, + GroupId: readGroup.Id, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + c.Convey("not exit repo", func() { + resp, err := client.InviteMember(ctx, user2.Name, "fake_repo", &api.InviteMemberParams{ + UserId: user1.Id, + GroupId: readGroup.Id, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("invite for other repo", func() { + resp, err := client.InviteMember(ctx, user1.Name, repo1.Name, &api.InviteMemberParams{ + UserId: user1.Id, + GroupId: readGroup.Id, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("cannot read permission before granted", func() { + client.RequestEditors = user1Token + resp, err := client.GetRepository(ctx, user2.Name, repo2.Name) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + client.RequestEditors = user2Token + }) + + c.Convey("invite success", func() { + resp, err := client.InviteMember(ctx, user2.Name, repo2.Name, &api.InviteMemberParams{ + UserId: user1.Id, + GroupId: readGroup.Id, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + }) + + c.Convey("invite duplicate", func() { + resp, err := client.InviteMember(ctx, user2.Name, repo2.Name, &api.InviteMemberParams{ + UserId: user1.Id, + GroupId: readGroup.Id, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusInternalServerError) + }) + + c.Convey("check read permission was granted", func() { + client.RequestEditors = user1Token + resp, err := client.GetRepository(ctx, user2.Name, repo2.Name) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + client.RequestEditors = user2Token + }) + + c.Convey("cannot write permission with read grant", func() { + client.RequestEditors = user1Token + resp, err := client.CreateBranch(ctx, user2.Name, repo2.Name, api.CreateBranchJSONRequestBody{ + Name: "testbranch", + Source: "main", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + client.RequestEditors = user2Token + }) + }) + + c.Convey("update member", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.UpdateMemberGroup(ctx, user2.Name, repo2.Name, &api.UpdateMemberGroupParams{ + UserId: user1.Id, + GroupId: writeGroup.Id, + }) + client.RequestEditors = re + convey.ShouldBeNil(c, err) + convey.ShouldBeNil(c, resp) + }) + + c.Convey("not exit owner", func() { + resp, err := client.UpdateMemberGroup(ctx, "fake_owner", repo2.Name, &api.UpdateMemberGroupParams{ + UserId: user1.Id, + GroupId: writeGroup.Id, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("not exit repo", func() { + resp, err := client.UpdateMemberGroup(ctx, user2.Name, "mock_repo", &api.UpdateMemberGroupParams{ + UserId: user1.Id, + GroupId: writeGroup.Id, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("update for other repo", func() { + resp, err := client.UpdateMemberGroup(ctx, user1.Name, repo1.Name, &api.UpdateMemberGroupParams{ + UserId: user1.Id, + GroupId: writeGroup.Id, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("update success", func() { + resp, err := client.UpdateMemberGroup(ctx, user2.Name, repo2.Name, &api.UpdateMemberGroupParams{ + UserId: user1.Id, + GroupId: writeGroup.Id, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + }) + + c.Convey("write permission with write grant", func() { + client.RequestEditors = user1Token + resp, err := client.CreateBranch(ctx, user2.Name, repo2.Name, api.CreateBranchJSONRequestBody{ + Name: "testbranch", + Source: "main", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + client.RequestEditors = user2Token + }) + }) + + c.Convey("revoke member", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.RevokeMember(ctx, user2.Name, repo2.Name, &api.RevokeMemberParams{ + UserId: user1.Id, + }) + client.RequestEditors = re + convey.ShouldBeNil(c, err) + convey.ShouldBeNil(c, resp) + }) + + c.Convey("not exit owner", func() { + resp, err := client.RevokeMember(ctx, "fake_owner", repo2.Name, &api.RevokeMemberParams{ + UserId: user1.Id, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("not exit repo", func() { + resp, err := client.RevokeMember(ctx, user2.Name, "mock_repo", &api.RevokeMemberParams{ + UserId: user1.Id, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("update for other repo", func() { + resp, err := client.RevokeMember(ctx, user1.Name, repo1.Name, &api.RevokeMemberParams{ + UserId: user1.Id, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("update success", func() { + resp, err := client.RevokeMember(ctx, user2.Name, repo2.Name, &api.RevokeMemberParams{ + UserId: user1.Id, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + }) + + c.Convey("write permission with write grant", func() { + client.RequestEditors = user1Token + resp, err := client.CreateBranch(ctx, user2.Name, repo2.Name, api.CreateBranchJSONRequestBody{ + Name: "testbranch", + Source: "main", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + client.RequestEditors = user2Token + }) + }) + + c.Convey("list member", func(c convey.C) { + c.Convey("init", func() { + _, err := client.InviteMember(ctx, user2.Name, repo2.Name, &api.InviteMemberParams{ + UserId: user1.Id, + GroupId: readGroup.Id, + }) + convey.So(err, convey.ShouldBeNil) + }) + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.ListMembers(ctx, user2.Name, repo2.Name) + client.RequestEditors = re + convey.ShouldBeNil(c, err) + convey.ShouldBeNil(c, resp) + }) + + c.Convey("not exit owner", func() { + resp, err := client.ListMembers(ctx, "fake_owner", repo2.Name) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("not exit repo", func() { + resp, err := client.ListMembers(ctx, user2.Name, "mock_repo") + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("list for other repo", func() { + resp, err := client.ListMembers(ctx, user1.Name, repo1.Name) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("update success", func() { + resp, err := client.ListMembers(ctx, user2.Name, repo2.Name) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseListMembersResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.ShouldHaveLength(1, len(*result.JSON200)) + }) + }) + } +} + +func getUser(ctx context.Context, client *api.Client) (*api.UserInfo, error) { + resp, err := client.GetUserInfo(ctx) + if err != nil { + return nil, err + } + result, err := api.ParseGetUserInfoResponse(resp) + if err != nil { + return nil, err + } + return result.JSON200, nil +} + +func getGroup(ctx context.Context, client *api.Client) (*api.Group, *api.Group, *api.Group, error) { + resp, err := client.ListRepoGroup(ctx) + if err != nil { + return nil, nil, nil, err + } + result, err := api.ParseListRepoGroupResponse(resp) + if err != nil { + return nil, nil, nil, err + } + var adminGroup, writeGroup, readGroup api.Group + for _, g := range *result.JSON200 { + if g.Name == rbac.RepoAdmin { + adminGroup = g + } + + if g.Name == rbac.RepoWrite { + writeGroup = g + } + + if g.Name == rbac.RepoRead { + readGroup = g + } + } + + return &readGroup, &writeGroup, &adminGroup, nil +} + +func getRepo(ctx context.Context, client *api.Client, owner, repoName string) (*api.Repository, error) { + resp, err := client.GetRepository(ctx, owner, repoName) + if err != nil { + return nil, err + } + result, err := api.ParseGetRepositoryResponse(resp) + if err != nil { + return nil, err + } + return result.JSON200, nil +} +func getToken(ctx context.Context, client *api.Client, userName string) ([]api.RequestEditorFn, error) { + resp, err := client.Login(ctx, api.LoginJSONRequestBody{ + Name: userName, + Password: "12345678", + }) + if err != nil { + return nil, err + } + loginResult, err := api.ParseLoginResponse(resp) + if err != nil { + return nil, err + } + + return []api.RequestEditorFn{func(ctx context.Context, req *http.Request) error { + req.Header.Add("Authorization", "Bearer "+loginResult.JSON200.Token) + return nil + }}, nil +} diff --git a/integrationtest/merge_request_test.go b/integrationtest/merge_request_test.go index 00d2403e..4811f690 100644 --- a/integrationtest/merge_request_test.go +++ b/integrationtest/merge_request_test.go @@ -24,13 +24,15 @@ func MergeRequestSpec(ctx context.Context, urlStr string) func(c convey.C) { repoName := "mr_test" branchName := "feat/obj_test" - createUser(ctx, c, client, userName) - loginAndSwitch(ctx, c, client, "molly login", userName, false) - createRepo(ctx, c, client, repoName) - createBranch(ctx, c, client, "create branch", userName, repoName, "main", branchName) - createWip(ctx, c, client, "feat get obj test", userName, repoName, branchName) - uploadObject(ctx, c, client, "update f1 to test branch", userName, repoName, branchName, "a.bin") - commitWip(ctx, c, client, "commit object", userName, repoName, branchName, "test") + c.Convey("init", func(c convey.C) { + createUser(ctx, client, userName) + loginAndSwitch(ctx, client, userName, false) + createRepo(ctx, client, repoName) + createBranch(ctx, client, userName, repoName, "main", branchName) + createWip(ctx, client, userName, repoName, branchName) + uploadObject(ctx, client, userName, repoName, branchName, "a.bin") + commitWip(ctx, client, userName, repoName, branchName, "test") + }) c.Convey("create merge request", func(c convey.C) { c.Convey("no auth", func() { @@ -88,7 +90,7 @@ func MergeRequestSpec(ctx context.Context, urlStr string) func(c convey.C) { Title: "Merge: test", }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("fail to create mergerequest from non exit branch", func() { @@ -176,7 +178,7 @@ func MergeRequestSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to get merge request from others user", func() { resp, err := client.GetMergeRequest(ctx, "jimmy", "happygo", *firstMrID) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("fail to get merge request with non exit mergerequest", func() { @@ -230,7 +232,7 @@ func MergeRequestSpec(ctx context.Context, urlStr string) func(c convey.C) { Title: utils.String("Merge: test title"), }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("success to update merge request", func() { @@ -252,14 +254,16 @@ func MergeRequestSpec(ctx context.Context, urlStr string) func(c convey.C) { }) }) - for i := 0; i < 10; i++ { - branchName := fmt.Sprintf("feat/list_merge_test_%d", i) - createBranch(ctx, c, client, fmt.Sprintf("create branch %d", i), userName, repoName, "main", branchName) - createWip(ctx, c, client, "feat list merge test "+strconv.Itoa(i), userName, repoName, branchName) - uploadObject(ctx, c, client, "update f1 to test branch "+strconv.Itoa(i), userName, repoName, branchName, fmt.Sprintf("%d.txt", i)) - commitWip(ctx, c, client, "commit object "+strconv.Itoa(i), userName, repoName, branchName, "test") - createMergeRequest(ctx, c, client, "create merge request "+strconv.Itoa(i), userName, repoName, branchName, "main") - } + c.Convey("create many mergequests", func(c convey.C) { + for i := 0; i < 10; i++ { + branchName := fmt.Sprintf("feat/list_merge_test_%d", i) + createBranch(ctx, client, userName, repoName, "main", branchName) + createWip(ctx, client, userName, repoName, branchName) + uploadObject(ctx, client, userName, repoName, branchName, fmt.Sprintf("%d.txt", i)) + commitWip(ctx, client, userName, repoName, branchName, "test") + createMergeRequest(ctx, client, userName, repoName, branchName, "main") + } + }) c.Convey("list merge request", func(c convey.C) { @@ -287,7 +291,7 @@ func MergeRequestSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to list merge request from others user", func() { resp, err := client.ListMergeRequests(ctx, "jimmy", "happygo", &api.ListMergeRequestsParams{}) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("success to list merge request", func() { @@ -384,7 +388,7 @@ func MergeRequestSpec(ctx context.Context, urlStr string) func(c convey.C) { Msg: "test merge", }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("fail to update merge request from non exit mr", func() { resp, err := client.Merge(ctx, userName, repoName, 100, api.MergeJSONRequestBody{ diff --git a/integrationtest/objects_test.go b/integrationtest/objects_test.go index 0472348f..8321b760 100644 --- a/integrationtest/objects_test.go +++ b/integrationtest/objects_test.go @@ -21,12 +21,13 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { repoName := "dataspace" branchName := "feat/obj_test" - createUser(ctx, c, client, userName) - loginAndSwitch(ctx, c, client, "molly login", userName, false) - createRepo(ctx, c, client, repoName) - createBranch(ctx, c, client, "create branch", userName, repoName, "main", branchName) - createWip(ctx, c, client, "feat get obj test", userName, repoName, branchName) - + c.Convey("init", func(c convey.C) { + createUser(ctx, client, userName) + loginAndSwitch(ctx, client, userName, false) + createRepo(ctx, client, repoName) + createBranch(ctx, client, userName, repoName, "main", branchName) + createWip(ctx, client, userName, repoName, branchName) + }) c.Convey("upload object", func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors @@ -82,7 +83,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { Path: "a.bin", }, "application/octet-stream", bytes.NewReader([]byte{1, 2, 3, 4, 5, 6, 7, 8})) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("empty path", func() { @@ -122,7 +123,9 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { }) //commit object to branch - commitWip(ctx, c, client, "commit wip", userName, repoName, branchName, "test commit msg") + c.Convey("commit object to branch", func(c convey.C) { + commitWip(ctx, client, userName, repoName, branchName, "test commit msg") + }) c.Convey("head object", func(c convey.C) { c.Convey("no auth", func() { @@ -175,7 +178,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { Type: api.RefTypeBranch, }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("empty path", func() { @@ -261,7 +264,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { Type: api.RefTypeBranch, }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("empty path", func() { @@ -293,8 +296,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) reader := hash.NewHashingReader(resp.Body, hash.Md5) - data, err := io.ReadAll(reader) - fmt.Println(data) + _, err = io.ReadAll(reader) convey.So(err, convey.ShouldBeNil) etag := resp.Header.Get("ETag") diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index 58b0a571..bf7a9b46 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -2,7 +2,6 @@ package integrationtest import ( "context" - "fmt" "net/http" "github.com/jiaozifs/jiaozifs/api" @@ -17,9 +16,15 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { return func(c convey.C) { userName := "jimmy" repoName := "happyrun" - createUser(ctx, c, client, userName) - loginAndSwitch(ctx, c, client, "jimmy login", userName, false) + c.Convey("init", func(c convey.C) { + loginAndSwitch(ctx, client, "admin2", true) + createRepo(ctx, client, "admin2_repo") + + createUser(ctx, client, userName) + loginAndSwitch(ctx, client, userName, false) + + }) c.Convey("create repo", func(c convey.C) { c.Convey("forbidden create repo name", func() { resp, err := client.CreateRepository(ctx, api.CreateRepository{ @@ -63,7 +68,6 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { grp, err := api.ParseCreateRepositoryResponse(resp) convey.So(err, convey.ShouldBeNil) convey.So(grp.JSON201.Head, convey.ShouldEqual, controller.DefaultBranchName) - fmt.Println(grp.JSON201.Id) //check default branch created branchResp, err := client.GetBranch(ctx, userName, grp.JSON201.Name, &api.GetBranchParams{RefName: controller.DefaultBranchName}) convey.So(err, convey.ShouldBeNil) @@ -241,9 +245,9 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("list others repository", func() { - resp, err := client.ListRepository(ctx, "admin", &api.ListRepositoryParams{Prefix: utils.String("bad")}) + resp, err := client.ListRepository(ctx, "admin2", &api.ListRepositoryParams{Prefix: utils.String("bad")}) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) }) @@ -280,9 +284,9 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("get other's repo", func() { - resp, err := client.GetRepository(ctx, "admin", repoName) + resp, err := client.GetRepository(ctx, "admin2", "admin2_repo") convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) }) @@ -335,11 +339,11 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("update repository in other's repo", func() { description := "" - resp, err := client.UpdateRepository(ctx, "admin", repoName, api.UpdateRepositoryJSONRequestBody{ + resp, err := client.UpdateRepository(ctx, "admin2", "admin2_repo", api.UpdateRepositoryJSONRequestBody{ Description: utils.String(description), }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("update head to not exit", func() { @@ -350,7 +354,10 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) }) - createBranch(ctx, c, client, "create branch", userName, repoName, "main", "feat/ano_branch") + c.Convey("create branch", func(c convey.C) { + createBranch(ctx, client, userName, repoName, "main", "feat/ano_branch") + }) + c.Convey("update default head success", func() { resp, err := client.UpdateRepository(ctx, userName, repoName, api.UpdateRepositoryJSONRequestBody{ Head: utils.String("feat/ano_branch"), @@ -388,17 +395,20 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("update repository in other's repo", func() { - resp, err := client.GetCommitsInRef(ctx, "admin", repoName, &api.GetCommitsInRefParams{ + resp, err := client.GetCommitsInRef(ctx, "admin2", "admin2_repo", &api.GetCommitsInRefParams{ RefName: utils.String(controller.DefaultBranchName), }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + + }) + c.Convey("add commit to branch", func(c convey.C) { + createWip(ctx, client, userName, repoName, controller.DefaultBranchName) + uploadObject(ctx, client, userName, repoName, controller.DefaultBranchName, "a.txt") + commitWip(ctx, client, userName, repoName, controller.DefaultBranchName, "first commit") }) - createWip(ctx, c, client, "add commit to random branch", userName, repoName, controller.DefaultBranchName) - uploadObject(ctx, c, client, "add rand object", userName, repoName, controller.DefaultBranchName, "a.txt") - commitWip(ctx, c, client, "commit object", userName, repoName, controller.DefaultBranchName, "first commit") c.Convey("success get commits", func() { resp, err := client.GetCommitsInRef(ctx, userName, repoName, &api.GetCommitsInRefParams{ RefName: utils.String(controller.DefaultBranchName), @@ -412,10 +422,13 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So((*result.JSON200)[0].Message, convey.ShouldEqual, "first commit") }) - uploadObject(ctx, c, client, "add sec object", userName, repoName, controller.DefaultBranchName, "b.txt") - commitWip(ctx, c, client, "commit sec object", userName, repoName, controller.DefaultBranchName, "second commit") - uploadObject(ctx, c, client, "add third object", userName, repoName, controller.DefaultBranchName, "c.txt") - commitWip(ctx, c, client, "commit third object", userName, repoName, controller.DefaultBranchName, "third commit") + c.Convey("add double commit to branch", func(c convey.C) { + uploadObject(ctx, client, userName, repoName, controller.DefaultBranchName, "b.txt") + commitWip(ctx, client, userName, repoName, controller.DefaultBranchName, "second commit") + uploadObject(ctx, client, userName, repoName, controller.DefaultBranchName, "c.txt") + commitWip(ctx, client, userName, repoName, controller.DefaultBranchName, "third commit") + }) + c.Convey("success get commits by params", func() { resp, err := client.GetCommitsInRef(ctx, userName, repoName, &api.GetCommitsInRefParams{ RefName: utils.String(controller.DefaultBranchName), @@ -465,9 +478,9 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("delete repository in other's repo", func() { - resp, err := client.DeleteRepository(ctx, "admin", repoName, &api.DeleteRepositoryParams{}) + resp, err := client.DeleteRepository(ctx, "admin2", "admin2_repo", &api.DeleteRepositoryParams{}) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("delete repository successful", func() { diff --git a/integrationtest/root_test.go b/integrationtest/root_test.go index 99fec43c..8e870cb5 100644 --- a/integrationtest/root_test.go +++ b/integrationtest/root_test.go @@ -24,5 +24,7 @@ func TestSpec(t *testing.T) { convey.Convey("update wip test", t, UpdateWipSpec(ctx, urlStr)) convey.Convey("get entries test", t, GetEntriesInRefSpec(ctx, urlStr)) convey.Convey("commit changes test", t, GetCommitChangesSpec(ctx, urlStr)) - convey.Convey("mergee request test", t, MergeRequestSpec(ctx, urlStr)) + convey.Convey("merge request test", t, MergeRequestSpec(ctx, urlStr)) + convey.Convey("group test", t, GroupSpec(ctx, urlStr)) + convey.Convey("member test", t, MemberSpec(ctx, urlStr)) } diff --git a/integrationtest/user_test.go b/integrationtest/user_test.go index ea3bc54c..f58cdcac 100644 --- a/integrationtest/user_test.go +++ b/integrationtest/user_test.go @@ -2,7 +2,6 @@ package integrationtest import ( "context" - "fmt" "net/http" openapi_types "github.com/oapi-codegen/runtime/types" @@ -15,14 +14,17 @@ import ( func UserSpec(ctx context.Context, urlStr string) func(c convey.C) { client, _ := api.NewClient(urlStr + apiimpl.APIV1Prefix) return func(c convey.C) { - userName := "admin" - createUser(ctx, c, client, userName) + userName := "admin2" + + c.Convey("init user", func(c convey.C) { + createUser(ctx, client, userName) + }) c.Convey("invalid username", func() { resp, err := client.Register(ctx, api.RegisterJSONRequestBody{ Name: "admin!@#", Password: "12345678", - Email: openapi_types.Email(fmt.Sprintf("mock%d@gmail.com", count)), + Email: openapi_types.Email("mock123@gmail.com"), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) @@ -36,14 +38,16 @@ func UserSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("login fail", func() { resp, err := client.Login(ctx, api.LoginJSONRequestBody{ - Name: "admin", + Name: "admin2", Password: " vvvvvvvv", }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) - loginAndSwitch(ctx, c, client, "admin login", userName, false) + c.Convey("admin login", func(c convey.C) { + loginAndSwitch(ctx, client, userName, false) + }) c.Convey("usr profile", func() { resp, err := client.GetUserInfo(ctx) @@ -51,7 +55,9 @@ func UserSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) }) - loginAndSwitch(ctx, c, client, "admin login again", userName, true) + c.Convey("admin login again", func(c convey.C) { + loginAndSwitch(ctx, client, userName, true) + }) c.Convey("usr profile with cookie", func() { resp, err := client.GetUserInfo(ctx) @@ -75,5 +81,6 @@ func UserSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) client.RequestEditors = re }) + } } diff --git a/integrationtest/wip_object_test.go b/integrationtest/wip_object_test.go index efe7b104..dc97e6cd 100644 --- a/integrationtest/wip_object_test.go +++ b/integrationtest/wip_object_test.go @@ -19,13 +19,15 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { repoName := "hash" branchName := "feat/wip_obj_test" - createUser(ctx, c, client, userName) - loginAndSwitch(ctx, c, client, "jude login", userName, false) - createRepo(ctx, c, client, repoName) - createBranch(ctx, c, client, "create branch", userName, repoName, "main", branchName) - createWip(ctx, c, client, "get wip obj test", userName, repoName, branchName) - uploadObject(ctx, c, client, "update f1 to test branch", userName, repoName, branchName, "m.dat") - uploadObject(ctx, c, client, "update f2 to test branch", userName, repoName, branchName, "g/m.dat") + c.Convey("init", func(c convey.C) { + createUser(ctx, client, userName) + loginAndSwitch(ctx, client, userName, false) + createRepo(ctx, client, repoName) + createBranch(ctx, client, userName, repoName, "main", branchName) + createWip(ctx, client, userName, repoName, branchName) + uploadObject(ctx, client, userName, repoName, branchName, "m.dat") + uploadObject(ctx, client, userName, repoName, branchName, "g/m.dat") + }) c.Convey("head object", func(c convey.C) { c.Convey("no auth", func() { @@ -78,7 +80,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("empty path", func() { @@ -162,7 +164,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { Type: api.RefTypeWip, }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("empty path", func() { @@ -241,7 +243,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { Path: "g/m.dat", }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("empty path", func() { @@ -279,7 +281,9 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) - commitWip(ctx, c, client, "commit delete object", userName, repoName, branchName, "test") + c.Convey("commit changes", func() { + commitWip(ctx, client, userName, repoName, branchName, "test") + }) //ensure not exit resp, err = client.HeadObject(ctx, userName, repoName, &api.HeadObjectParams{ @@ -291,15 +295,17 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) }) }) + testBranchName := "test/empty_branch" - uploadObject(ctx, c, client, "update f3 to test branch", userName, repoName, branchName, "a/m.dat") - uploadObject(ctx, c, client, "update f4 to test branch", userName, repoName, branchName, "a/b.dat") - uploadObject(ctx, c, client, "update f5 to test branch", userName, repoName, branchName, "b.dat") - uploadObject(ctx, c, client, "update f6 to test branch", userName, repoName, branchName, "c.dat") + c.Convey("update objects and create empty branch", func(c convey.C) { + uploadObject(ctx, client, userName, repoName, branchName, "a/m.dat") + uploadObject(ctx, client, userName, repoName, branchName, "a/b.dat") + uploadObject(ctx, client, userName, repoName, branchName, "b.dat") + uploadObject(ctx, client, userName, repoName, branchName, "c.dat") - testBranchName := "test/empty_branch" - createBranch(ctx, c, client, "create empty branch", userName, repoName, "main", testBranchName) - createWip(ctx, c, client, "create empty_branch wip", userName, repoName, testBranchName) + createBranch(ctx, client, userName, repoName, "main", testBranchName) + createWip(ctx, client, userName, repoName, testBranchName) + }) c.Convey("get wip success on init", func(c convey.C) { resp, err := client.GetWipChanges(ctx, userName, repoName, &api.GetWipChangesParams{ @@ -354,7 +360,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { RefName: "main", }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("not exit path", func() { @@ -467,17 +473,20 @@ func UpdateWipSpec(ctx context.Context, urlStr string) func(c convey.C) { userName := "milly" repoName := "update_wip_test" branchName := "main" - createUser(ctx, c, client, userName) - loginAndSwitch(ctx, c, client, "jude login", userName, false) - createRepo(ctx, c, client, repoName) - createWip(ctx, c, client, "get wip obj test", userName, repoName, branchName) - //make wip base commit has value - uploadObject(ctx, c, client, "update init object", userName, repoName, branchName, "a.txt") - commitWip(ctx, c, client, "commit init object", userName, repoName, branchName, "test") + c.Convey("create wip", func(c convey.C) { + createUser(ctx, client, userName) + loginAndSwitch(ctx, client, userName, false) + createRepo(ctx, client, repoName) + createWip(ctx, client, userName, repoName, branchName) - uploadObject(ctx, c, client, "update f1 to test branch", userName, repoName, branchName, "m.dat") - uploadObject(ctx, c, client, "update f2 to test branch", userName, repoName, branchName, "g/m.dat") + //make wip base commit has value + uploadObject(ctx, client, userName, repoName, branchName, "a.txt") + commitWip(ctx, client, userName, repoName, branchName, "test") + + uploadObject(ctx, client, userName, repoName, branchName, "m.dat") + uploadObject(ctx, client, userName, repoName, branchName, "g/m.dat") + }) c.Convey("get wip", func(c convey.C) { resp, err := client.GetWip(ctx, userName, repoName, &api.GetWipParams{ @@ -528,7 +537,7 @@ func UpdateWipSpec(ctx context.Context, urlStr string) func(c convey.C) { BaseCommit: utils.String(hash.Empty.Hex()), }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) c.Convey("update wip in non exit branch", func() { diff --git a/integrationtest/wip_test.go b/integrationtest/wip_test.go index add3eaa4..198738dd 100644 --- a/integrationtest/wip_test.go +++ b/integrationtest/wip_test.go @@ -17,11 +17,14 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { branchName := "feat/wip_test" branchNameForDelete := "feat/wip_test2" - createUser(ctx, c, client, userName) - loginAndSwitch(ctx, c, client, "july login", userName, false) - createRepo(ctx, c, client, repoName) - createBranch(ctx, c, client, "create branch", userName, repoName, "main", branchName) - createBranch(ctx, c, client, "create branch for delete", userName, repoName, "main", branchNameForDelete) + c.Convey("init", func(c convey.C) { + createUser(ctx, client, userName) + loginAndSwitch(ctx, client, userName, false) + createRepo(ctx, client, repoName) + createBranch(ctx, client, userName, repoName, "main", branchName) + createBranch(ctx, client, userName, repoName, "main", branchNameForDelete) + }) + c.Convey("list non exit wip", func(c convey.C) { resp, err := client.ListWip(ctx, userName, repoName) convey.So(err, convey.ShouldBeNil) @@ -32,7 +35,9 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(respResult.JSON200, convey.ShouldHaveLength, 0) }) - createWip(ctx, c, client, "create main wip", userName, repoName, "main") + c.Convey("create wip", func() { + createWip(ctx, client, userName, repoName, "main") + }) c.Convey("get wip", func(c convey.C) { c.Convey("no auth", func() { @@ -97,7 +102,7 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { RefName: "main", }) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) }) @@ -136,7 +141,7 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("fail to list wip in others's repo", func() { resp, err := client.ListWip(ctx, "jimmy", "happygo") convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) }) @@ -171,10 +176,13 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("delete wip in other's repo", func() { resp, err := client.DeleteWip(ctx, "jimmy", "happygo", &api.DeleteWipParams{RefName: "main"}) convey.So(err, convey.ShouldBeNil) - convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusForbidden) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("creat wip for test delete", func() { + createWip(ctx, client, userName, repoName, branchNameForDelete) }) - createWip(ctx, c, client, "creat wip for test delete", userName, repoName, branchNameForDelete) c.Convey("delete branch successful", func() { //delete resp, err := client.DeleteWip(ctx, userName, repoName, &api.DeleteWipParams{RefName: branchNameForDelete}) diff --git a/makefile b/makefile index d324eb6d..33487110 100644 --- a/makefile +++ b/makefile @@ -15,7 +15,7 @@ GOFLAGS+=-ldflags="$(ldflags)" gen-api: ./api/swagger.yml ./api/tmpls/chi $(GOGENERATE) ./api - + $(GOGENERATE) ./models/rbacmodel install-go-swagger: go install github.com/go-swagger/go-swagger/cmd/swagger@latest diff --git a/models/aksk_test.go b/models/aksk_test.go index f0387c63..1764c192 100644 --- a/models/aksk_test.go +++ b/models/aksk_test.go @@ -29,7 +29,7 @@ func TestAkskRepo_Delete(t *testing.T) { expectAksk, err := repo.Get(ctx, models.NewGetAkSkParams().SetAccessKey(aksk.AccessKey).SetID(aksk.ID).SetUserID(aksk.UserID)) require.NoError(t, err) - require.True(t, cmp.Equal(expectAksk, aksk, dbTimeCmpOpt)) + require.True(t, cmp.Equal(expectAksk, aksk, testhelper.DBTimeCmpOpt)) }) t.Run("list", func(t *testing.T) { userID := uuid.New() diff --git a/models/branch_test.go b/models/branch_test.go index 0c09fe15..6d44b6a4 100644 --- a/models/branch_test.go +++ b/models/branch_test.go @@ -34,7 +34,7 @@ func TestRefRepoInsert(t *testing.T) { branch, err := repo.Get(ctx, getBranchParams) require.NoError(t, err) - require.True(t, cmp.Equal(branchModel, branch, dbTimeCmpOpt)) + require.True(t, cmp.Equal(branchModel, branch, testhelper.DBTimeCmpOpt)) mockHash := hash.Hash("mock hash") err = repo.UpdateByID(ctx, models.NewUpdateBranchParams(newBranch.ID).SetCommitHash(mockHash)) @@ -64,7 +64,7 @@ func TestRefRepoInsert(t *testing.T) { sRef, err := repo.Get(ctx, getSecRefParams) require.NoError(t, err) - require.True(t, cmp.Equal(secModel, sRef, dbTimeCmpOpt)) + require.True(t, cmp.Equal(secModel, sRef, testhelper.DBTimeCmpOpt)) // ExactMatch list1, hasMore, err := repo.List(ctx, models.NewListBranchParams().SetRepositoryID(branch.RepositoryID).SetName(secModel.Name, models.ExactMatch).SetAmount(1)) diff --git a/models/commit_test.go b/models/commit_test.go index d1fc1bae..ff767762 100644 --- a/models/commit_test.go +++ b/models/commit_test.go @@ -29,7 +29,7 @@ func TestCommitRepo(t *testing.T) { commitModel, err = commitRepo.Commit(ctx, commitModel.Hash) require.NoError(t, err) - require.True(t, cmp.Equal(commitModel, newCommitModel, dbTimeCmpOpt)) + require.True(t, cmp.Equal(commitModel, newCommitModel, testhelper.DBTimeCmpOpt)) t.Run("mis match repo id", func(t *testing.T) { mistMatchModel := &models.Commit{} diff --git a/models/members.go b/models/members.go new file mode 100644 index 00000000..3b43270e --- /dev/null +++ b/models/members.go @@ -0,0 +1,212 @@ +package models + +import ( + "context" + "time" + + "github.com/google/uuid" + "github.com/uptrace/bun" +) + +type Member struct { + bun.BaseModel `bun:"table:members"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()" json:"id"` + UserID uuid.UUID `bun:"user_id,type:uuid,unique:user_repo_pk,notnull" json:"user_id"` + RepoID uuid.UUID `bun:"repo_id,type:uuid,unique:user_repo_pk,notnull" json:"repo_id"` + GroupID uuid.UUID `bun:"group_id,type:uuid,notnull" json:"group_id"` + // CreatedAt + CreatedAt time.Time `bun:"created_at,type:timestamp,notnull" json:"created_at"` + // UpdatedAt + UpdatedAt time.Time `bun:"updated_at,type:timestamp,notnull" json:"updated_at"` +} + +type GetMemberParams struct { + id uuid.UUID + repoID uuid.UUID + userID uuid.UUID +} + +func NewGetMemberParams() *GetMemberParams { + return &GetMemberParams{} +} + +func (p *GetMemberParams) SetID(id uuid.UUID) *GetMemberParams { + p.id = id + return p +} + +func (p *GetMemberParams) SetRepoID(repo uuid.UUID) *GetMemberParams { + p.repoID = repo + return p +} + +func (p *GetMemberParams) SetUserID(userID uuid.UUID) *GetMemberParams { + p.userID = userID + return p +} + +type DeleteMemberParams struct { + repoID uuid.UUID + userID uuid.UUID +} + +func NewDeleteMemberParams() *DeleteMemberParams { + return &DeleteMemberParams{} +} + +func (p *DeleteMemberParams) SetRepoID(repo uuid.UUID) *DeleteMemberParams { + p.repoID = repo + return p +} + +func (p *DeleteMemberParams) SetUserID(userID uuid.UUID) *DeleteMemberParams { + p.userID = userID + return p +} + +type UpdateMemberParams struct { + filter struct { + repoID uuid.UUID + userID uuid.UUID + } + + update struct { + GroupID uuid.UUID + } + + updateTime time.Time +} + +func NewUpdateMemberParams() *UpdateMemberParams { + return &UpdateMemberParams{ + updateTime: time.Now(), + } +} + +func (p *UpdateMemberParams) SetFilterRepoID(repo uuid.UUID) *UpdateMemberParams { + p.filter.repoID = repo + return p +} + +func (p *UpdateMemberParams) SetFilterUserID(userID uuid.UUID) *UpdateMemberParams { + p.filter.userID = userID + return p +} + +func (p *UpdateMemberParams) SetUpdateGroupID(groupID uuid.UUID) *UpdateMemberParams { + p.update.GroupID = groupID + return p +} + +type ListMembersParams struct { + repoID uuid.UUID +} + +func NewListMembersParams() *ListMembersParams { + return &ListMembersParams{} +} + +func (p *ListMembersParams) SetRepoID(repo uuid.UUID) *ListMembersParams { + p.repoID = repo + return p +} + +type IMemberRepo interface { + Insert(ctx context.Context, member *Member) (*Member, error) + GetMember(ctx context.Context, params *GetMemberParams) (*Member, error) + ListMember(ctx context.Context, params *ListMembersParams) ([]*Member, error) + DeleteMember(ctx context.Context, params *DeleteMemberParams) (int64, error) + UpdateMember(ctx context.Context, params *UpdateMemberParams) error +} + +var _ IMemberRepo = (*MemberRepo)(nil) + +type MemberRepo struct { + db bun.IDB +} + +func NewMemberRepo(db bun.IDB) IMemberRepo { + return &MemberRepo{db: db} +} + +func (a MemberRepo) Insert(ctx context.Context, member *Member) (*Member, error) { + _, err := a.db.NewInsert().Model(member).Exec(ctx) + if err != nil { + return nil, err + } + return member, nil +} + +func (a MemberRepo) GetMember(ctx context.Context, params *GetMemberParams) (*Member, error) { + member := &Member{} + query := a.db.NewSelect().Model(member) + + if params.id != uuid.Nil { + query = query.Where("id = ?", params.id) + } + + if params.repoID != uuid.Nil { + query = query.Where("repo_id = ?", params.repoID) + } + if params.userID != uuid.Nil { + query = query.Where("user_id = ?", params.userID) + } + return member, query.Limit(1).Scan(ctx) +} +func (a MemberRepo) ListMember(ctx context.Context, params *ListMembersParams) ([]*Member, error) { + var members []*Member + query := a.db.NewSelect().Model(&members) + + if uuid.Nil != params.repoID { + query = query.Where("repo_id = ?", params.repoID) + } + + query = query.Order("created_at DESC") + + err := query.Scan(ctx) + if err != nil { + return nil, err + } + return members, nil +} + +func (a MemberRepo) UpdateMember(ctx context.Context, params *UpdateMemberParams) error { + updateQuery := a.db.NewUpdate().Model((*Member)(nil)) + if params.filter.repoID != uuid.Nil { + updateQuery.Where("repo_id = ?", params.filter.repoID) + } + if params.filter.userID != uuid.Nil { + updateQuery.Where("user_id = ?", params.filter.userID) + } + + if params.update.GroupID != uuid.Nil { + updateQuery.Set("group_id = ?", params.update.GroupID) + } + + updateQuery.Set("updated_at = ?", params.updateTime) + + _, err := updateQuery.Exec(ctx) + return err +} + +func (a MemberRepo) DeleteMember(ctx context.Context, params *DeleteMemberParams) (int64, error) { + query := a.db.NewDelete().Model((*Member)(nil)) + + if uuid.Nil != params.repoID { + query = query.Where("repo_id = ?", params.repoID) + } + + if uuid.Nil != params.userID { + query = query.Where("user_id = ?", params.userID) + } + + sqlResult, err := query.Exec(ctx) + if err != nil { + return 0, err + } + affectedRows, err := sqlResult.RowsAffected() + if err != nil { + return 0, err + } + return affectedRows, err +} diff --git a/models/members_test.go b/models/members_test.go new file mode 100644 index 00000000..f7a84297 --- /dev/null +++ b/models/members_test.go @@ -0,0 +1,121 @@ +package models_test + +import ( + "context" + "testing" + "time" + + "github.com/jiaozifs/jiaozifs/utils" + + "github.com/brianvoe/gofakeit/v6" + "github.com/google/go-cmp/cmp" + "github.com/google/uuid" + "github.com/stretchr/testify/require" + + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/testhelper" +) + +func TestMemberRepo(t *testing.T) { + ctx := context.Background() + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() + + memberRepo := models.NewMemberRepo(db) + + t.Run("insert and get", func(t *testing.T) { + memberModel := &models.Member{} + require.NoError(t, gofakeit.Struct(memberModel)) + + newMemberModel, err := memberRepo.Insert(ctx, memberModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newMemberModel.ID) + + getMRParams := models.NewGetMemberParams().SetID(newMemberModel.ID).SetUserID(newMemberModel.UserID).SetRepoID(newMemberModel.RepoID) + actualMember, err := memberRepo.GetMember(ctx, getMRParams) + require.NoError(t, err) + + require.True(t, cmp.Equal(actualMember, newMemberModel, testhelper.DBTimeCmpOpt)) + }) + + t.Run("user repo unique", func(t *testing.T) { + memberModel := &models.Member{} + require.NoError(t, gofakeit.Struct(memberModel)) + + newMemberModel, err := memberRepo.Insert(ctx, memberModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newMemberModel.ID) + + memberModel.GroupID = uuid.New() + _, err = memberRepo.Insert(ctx, memberModel) + require.Error(t, err) + }) + + t.Run("list member", func(t *testing.T) { + repoID := uuid.New() + var members []*models.Member + for i := 0; i < 10; i++ { + memberModel := &models.Member{} + require.NoError(t, gofakeit.Struct(memberModel)) + memberModel.RepoID = repoID + memberModel.CreatedAt = time.Now() + newMemberModel, err := memberRepo.Insert(ctx, memberModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newMemberModel.ID) + members = append(members, memberModel) + } + + listMemberParams := models.NewListMembersParams().SetRepoID(repoID) + listMembers, err := memberRepo.ListMember(ctx, listMemberParams) + require.NoError(t, err) + require.True(t, cmp.Equal(listMembers, utils.Reverse(members), testhelper.DBTimeCmpOpt)) + }) + + t.Run("delete member", func(t *testing.T) { + repoID := uuid.New() + var firstUserID uuid.UUID + for i := 0; i < 10; i++ { + memberModel := &models.Member{} + require.NoError(t, gofakeit.Struct(memberModel)) + memberModel.RepoID = repoID + newMemberModel, err := memberRepo.Insert(ctx, memberModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newMemberModel.ID) + if i == 0 { + firstUserID = memberModel.UserID + } + } + + //delete by repo and user + deleteMemberParams := models.NewDeleteMemberParams().SetRepoID(repoID).SetUserID(firstUserID) + deletedRows, err := memberRepo.DeleteMember(ctx, deleteMemberParams) + require.NoError(t, err) + require.Equal(t, 1, int(deletedRows)) + + //delete by repo + deleteMemberParams = models.NewDeleteMemberParams().SetRepoID(repoID) + deletedRows, err = memberRepo.DeleteMember(ctx, deleteMemberParams) + require.NoError(t, err) + require.Equal(t, 9, int(deletedRows)) + + }) + + t.Run("update repo", func(t *testing.T) { + memberModel := &models.Member{} + require.NoError(t, gofakeit.Struct(memberModel)) + + newMemberModel, err := memberRepo.Insert(ctx, memberModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newMemberModel.ID) + + newGroupID := uuid.New() + updateMemberParams := models.NewUpdateMemberParams().SetFilterRepoID(memberModel.RepoID).SetFilterUserID(memberModel.UserID).SetUpdateGroupID(newGroupID) + err = memberRepo.UpdateMember(ctx, updateMemberParams) + require.NoError(t, err) + + member, err := memberRepo.GetMember(ctx, models.NewGetMemberParams().SetRepoID(memberModel.RepoID).SetUserID(memberModel.UserID)) + require.NoError(t, err) + require.Equal(t, newGroupID, member.GroupID) + + }) +} diff --git a/models/merge_request_test.go b/models/merge_request_test.go index 2e0e498f..b773fe1d 100644 --- a/models/merge_request_test.go +++ b/models/merge_request_test.go @@ -38,7 +38,7 @@ func TestMergeRequestRepoInsert(t *testing.T) { mrModel, err = mrRepo.Get(ctx, getMRParams) require.NoError(t, err) - require.True(t, cmp.Equal(mrModel, newMrModel, dbTimeCmpOpt)) + require.True(t, cmp.Equal(mrModel, newMrModel, testhelper.DBTimeCmpOpt)) }) t.Run("delete", func(t *testing.T) { diff --git a/models/migrations/20210505110026_init_project.go b/models/migrations/20210505110026_init_project.go index a9b00661..b088b3bb 100644 --- a/models/migrations/20210505110026_init_project.go +++ b/models/migrations/20210505110026_init_project.go @@ -4,6 +4,7 @@ import ( "context" "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/models/rbacmodel" "github.com/uptrace/bun" ) @@ -87,6 +88,35 @@ func init() { if err != nil { return err } + + _, err = db.NewCreateTable(). + Model((*models.Member)(nil)). + Exec(ctx) + if err != nil { + return err + } + + _, err = db.NewCreateTable(). + Model((*rbacmodel.Group)(nil)). + Exec(ctx) + if err != nil { + return err + } + + _, err = db.NewCreateTable(). + Model((*rbacmodel.Policy)(nil)). + Exec(ctx) + if err != nil { + return err + } + + _, err = db.NewCreateTable(). + Model((*rbacmodel.UserGroup)(nil)). + Exec(ctx) + if err != nil { + return err + } + return err }, nil) } diff --git a/models/models.go b/models/models.go index 6042c0f1..f2452fc6 100644 --- a/models/models.go +++ b/models/models.go @@ -13,18 +13,11 @@ import ( ) func SetupDatabase(ctx context.Context, lc fx.Lifecycle, dbConfig *config.DatabaseConfig) (*bun.DB, error) { - sqlDB := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dbConfig.Connection))) - _, err := sqlDB.Conn(ctx) + bunDB, err := NewBunDBFromConfig(ctx, dbConfig) if err != nil { return nil, err } - bunDB := bun.NewDB(sqlDB, pgdialect.New(), bun.WithDiscardUnknownColumns()) - - if dbConfig.Debug { - bunDB.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true))) - } - lc.Append(fx.Hook{ OnStop: func(ctx context.Context) error { return bunDB.Close() @@ -33,3 +26,18 @@ func SetupDatabase(ctx context.Context, lc fx.Lifecycle, dbConfig *config.Databa return bunDB, nil } + +func NewBunDBFromConfig(ctx context.Context, dbConfig *config.DatabaseConfig) (*bun.DB, error) { + sqlDB := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dbConfig.Connection))) + _, err := sqlDB.Conn(ctx) + if err != nil { + return nil, err + } + + bunDB := bun.NewDB(sqlDB, pgdialect.New(), bun.WithDiscardUnknownColumns()) + + if dbConfig.Debug { + bunDB.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true))) + } + return bunDB, err +} diff --git a/models/rbacmodel/actions.gen.go b/models/rbacmodel/actions.gen.go new file mode 100644 index 00000000..1b79f025 --- /dev/null +++ b/models/rbacmodel/actions.gen.go @@ -0,0 +1,58 @@ +// Code generated by extract_actions. DO NOT EDIT. +// +package rbacmodel + +var Actions = []string{ + "repo:ReadRepository", + "repo:CreateRepository", + "repo:UpdateRepository", + "repo:DeleteRepository", + "repo:ListRepositories", + "repo:ReadObject", + "repo:WriteObject", + "repo:DeleteObject", + "repo:ListObjects", + "repo:CreateCommit", + "repo:ReadCommit", + "repo:ListCommits", + "repo:CreateBranch", + "repo:DeleteBranch", + "repo:ReadBranch", + "repo:ReadBranch", + "repo:ListBranches", + "repo:GetWip", + "repo:ListWip", + "repo:WriteWip", + "repo:CreateWip", + "repo:DeleteWip", + "repo:ReadConfig", + "repo:WriteConfig", + "repo:CreateMergeRequest", + "repo:ReadMergeRequest", + "repo:UpdateMergeRequest", + "repo:ListMergeRequest", + "repo:MergeMergeRequest", + "repo:AddGroupMember", + "repo:RemoveGroupMember", + "repo:GetGroupMember", + "repo:GetGroupMember", + "auth:ReadGroup", + "auth:CreateGroup", + "auth:DeleteGroup", + "auth:ListGroups", + "auth:ReadPolicy", + "auth:CreatePolicy", + "auth:UpdatePolicy", + "auth:DeletePolicy", + "auth:ListPolicies", + "auth:AttachPolicy", + "auth:DetachPolicy", + "user:UserProfile", + "user:ReadUser", + "user:ListUsers", + "user:DeleteUser", + "user:ReadCredentials", + "user:CreateCredentials", + "user:DeleteCredentials", + "user:ListCredentials", +} diff --git a/models/rbacmodel/actions.go b/models/rbacmodel/actions.go new file mode 100644 index 00000000..cd69dbfb --- /dev/null +++ b/models/rbacmodel/actions.go @@ -0,0 +1,96 @@ +//go:generate go run --tags generate ./codegen ./actions.go ./actions.gen.go + +package rbacmodel + +import ( + "errors" + "fmt" + "strings" +) + +var ( + ErrInvalidAction = errors.New("invalid action") + ErrInvalidServiceName = errors.New("invalid service name") +) + +const ( + ReadRepositoryAction = "repo:ReadRepository" + CreateRepositoryAction = "repo:CreateRepository" + UpdateRepositoryAction = "repo:UpdateRepository" + DeleteRepositoryAction = "repo:DeleteRepository" + ListRepositoriesAction = "repo:ListRepositories" + + ReadObjectAction = "repo:ReadObject" + WriteObjectAction = "repo:WriteObject" + DeleteObjectAction = "repo:DeleteObject" + ListObjectsAction = "repo:ListObjects" + + CreateCommitAction = "repo:CreateCommit" + ReadCommitAction = "repo:ReadCommit" + ListCommitsAction = "repo:ListCommits" + + CreateBranchAction = "repo:CreateBranch" + DeleteBranchAction = "repo:DeleteBranch" + ReadBranchAction = "repo:ReadBranch" + WriteBranchAction = "repo:ReadBranch" + ListBranchesAction = "repo:ListBranches" + + ReadWipAction = "repo:GetWip" + ListWipAction = "repo:ListWip" + WriteWipAction = "repo:WriteWip" + CreateWipAction = "repo:CreateWip" + DeleteWipAction = "repo:DeleteWip" + + ReadConfigAction = "repo:ReadConfig" + WriteConfigAction = "repo:WriteConfig" + + CreateMergeRequestAction = "repo:CreateMergeRequest" + ReadMergeRequestAction = "repo:ReadMergeRequest" + UpdateMergeRequestAction = "repo:UpdateMergeRequest" + ListMergeRequestAction = "repo:ListMergeRequest" + MergeMergeRequestAction = "repo:MergeMergeRequest" + + AddGroupMemberAction = "repo:AddGroupMember" + RemoveGroupMemberAction = "repo:RemoveGroupMember" + GetGroupMemberAction = "repo:GetGroupMember" + ListGroupMemberAction = "repo:GetGroupMember" + + ReadGroupAction = "auth:ReadGroup" + CreateGroupAction = "auth:CreateGroup" + DeleteGroupAction = "auth:DeleteGroup" + ListGroupsAction = "auth:ListGroups" + ReadPolicyAction = "auth:ReadPolicy" + CreatePolicyAction = "auth:CreatePolicy" + UpdatePolicyAction = "auth:UpdatePolicy" + DeletePolicyAction = "auth:DeletePolicy" + ListPoliciesAction = "auth:ListPolicies" + AttachPolicyAction = "auth:AttachPolicy" + DetachPolicyAction = "auth:DetachPolicy" + + UserProfileAction = "user:UserProfile" + ReadUserAction = "user:ReadUser" + ListUsersAction = "user:ListUsers" + DeleteUserAction = "user:DeleteUser" + ReadCredentialsAction = "user:ReadCredentials" + CreateCredentialsAction = "user:CreateCredentials" + DeleteCredentialsAction = "user:DeleteCredentials" + ListCredentialsAction = "user:ListCredentials" +) + +var serviceSet = map[string]struct{}{ + "repo": {}, + "auth": {}, + "user": {}, +} + +func IsValidAction(name string) error { + parts := strings.Split(name, ":") + const actionParts = 2 + if len(parts) != actionParts { + return fmt.Errorf("%s: %w", name, ErrInvalidAction) + } + if _, ok := serviceSet[parts[0]]; !ok { + return fmt.Errorf("%s: %w", name, ErrInvalidServiceName) + } + return nil +} diff --git a/models/rbacmodel/actions_test.go b/models/rbacmodel/actions_test.go new file mode 100644 index 00000000..a557b383 --- /dev/null +++ b/models/rbacmodel/actions_test.go @@ -0,0 +1,24 @@ +package rbacmodel_test + +import ( + "testing" + + "github.com/jiaozifs/jiaozifs/models/rbacmodel" + "golang.org/x/exp/slices" +) + +func TestAllActions(t *testing.T) { + actions := rbacmodel.Actions + + if !slices.Contains(actions, rbacmodel.ReadUserAction) { + t.Errorf("Expected actions %v to include %s", actions, rbacmodel.ReadUserAction) + } + + if !slices.Contains(actions, rbacmodel.ReadRepositoryAction) { + t.Errorf("Expected actions %v to include %s", actions, rbacmodel.ReadRepositoryAction) + } + + if slices.Contains(actions, "IsValidAction") { + t.Errorf("Expected actions %v not to include IsValidAction", actions) + } +} diff --git a/models/rbacmodel/codegen/main.go b/models/rbacmodel/codegen/main.go new file mode 100644 index 00000000..dfd30ec2 --- /dev/null +++ b/models/rbacmodel/codegen/main.go @@ -0,0 +1,76 @@ +//go:build generate + +package main + +import ( + "fmt" + "go/ast" + "go/importer" + "go/parser" + "go/token" + "go/types" + "log" + "os" +) + +func main() { + inFile := os.Args[1] + outFile := os.Args[2] + + out, err := os.Create(outFile) + if err != nil { + log.Fatalln(err) + } + + constantValues := []string{} + ConstantsOf(inFile, func(v string) { + constantValues = append(constantValues, v) + }) + + fmt.Fprintln(out, `// Code generated by extract_actions. DO NOT EDIT. +// +package rbacmodel + +var Actions = []string{`) + for _, c := range constantValues { + fmt.Fprintf(out, "\t%s,\n", c) + } + fmt.Fprintln(out, "}") +} + +func ConstantsOf(file string, value func(string)) { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, file, nil, 0) + if err != nil { + log.Fatalln(err) + } + + // Obtain type information. + conf := types.Config{Importer: importer.Default()} + info := &types.Info{ + Defs: make(map[*ast.Ident]types.Object), + } + _, err = conf.Check("p", fset, []*ast.File{f}, info) + if err != nil { + log.Fatal(err) + } + + for _, d := range f.Decls { + genDecl, ok := d.(*ast.GenDecl) + if !ok { + continue + } + for _, s := range genDecl.Specs { + v, ok := s.(*ast.ValueSpec) + if !ok { + continue + } + for _, name := range v.Names { + c, ok := info.ObjectOf(name).(*types.Const) + if ok { + value(c.Val().ExactString()) + } + } + } + } +} diff --git a/models/rbacmodel/const.go b/models/rbacmodel/const.go new file mode 100644 index 00000000..6d047699 --- /dev/null +++ b/models/rbacmodel/const.go @@ -0,0 +1,6 @@ +package rbacmodel + +const ( + StatementEffectAllow = "allow" + StatementEffectDeny = "deny" +) diff --git a/models/rbacmodel/group.go b/models/rbacmodel/group.go new file mode 100644 index 00000000..cc5d270e --- /dev/null +++ b/models/rbacmodel/group.go @@ -0,0 +1,125 @@ +package rbacmodel + +import ( + "context" + "time" + + "github.com/google/uuid" + "github.com/uptrace/bun" +) + +type Group struct { + bun.BaseModel `bun:"table:groups"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()" json:"id"` + // Name policy name + Name string `bun:"name,unique,notnull" json:"secret_key"` + // Policies + Policies []uuid.UUID `bun:"policies,type:jsonb,notnull" json:"policies"` + // CreatedAt + CreatedAt time.Time `bun:"created_at,type:timestamp,notnull" json:"created_at"` + // UpdatedAt + UpdatedAt time.Time `bun:"updated_at,type:timestamp,notnull" json:"updated_at"` +} + +type GetGroupParams struct { + id uuid.UUID + name *string +} + +func NewGetGroupParams() *GetGroupParams { + return &GetGroupParams{} +} + +func (gup *GetGroupParams) SetID(id uuid.UUID) *GetGroupParams { + gup.id = id + return gup +} + +func (gup *GetGroupParams) SetName(name string) *GetGroupParams { + gup.name = &name + return gup +} + +type ListGroupParams struct { + names []string +} + +func NewListGroupParams() *ListGroupParams { + return &ListGroupParams{} +} + +func (gup *ListGroupParams) SetNames(names ...string) *ListGroupParams { + gup.names = names + return gup +} + +type IGroupRepo interface { + GetGroupByUserID(ctx context.Context, userID uuid.UUID) (*Group, error) + Get(ctx context.Context, params *GetGroupParams) (*Group, error) + List(ctx context.Context, params *ListGroupParams) ([]*Group, error) + Insert(ctx context.Context, asSk *Group) (*Group, error) +} + +var _ IGroupRepo = (*GroupRepo)(nil) + +type GroupRepo struct { + db bun.IDB +} + +func NewGroupRepo(db bun.IDB) IGroupRepo { + return &GroupRepo{db: db} +} + +func (a GroupRepo) GetGroupByUserID(ctx context.Context, userID uuid.UUID) (*Group, error) { + ug := &Group{} + query := a.db.NewSelect().Model(ug) + + query = query.Join(`RIGHT JOIN usergroup ON usergroup.group_id = "group".id`).Where("usergroup.user_id = ?", userID) + err := query.Limit(1).Scan(ctx) + if err != nil { + return nil, err + } + return ug, nil +} + +func (a GroupRepo) Get(ctx context.Context, params *GetGroupParams) (*Group, error) { + ug := &Group{} + query := a.db.NewSelect().Model(ug) + + if uuid.Nil != params.id { + query = query.Where("id = ?", params.id) + } + if params.name != nil { + query = query.Where("name = ?", params.name) + } + err := query.Limit(1).Scan(ctx) + if err != nil { + return nil, err + } + return ug, nil +} + +func (a GroupRepo) List(ctx context.Context, params *ListGroupParams) ([]*Group, error) { + var groups []*Group + query := a.db.NewSelect().Model(&groups) + + if len(params.names) > 0 { + query = query.Where("name IN (?)", bun.In(params.names)) + } + + query = query.Order("created_at DESC") + + err := query.Scan(ctx) + if err != nil { + return nil, err + } + return groups, nil +} + +func (a GroupRepo) Insert(ctx context.Context, group *Group) (*Group, error) { + _, err := a.db.NewInsert().Model(group).Exec(ctx) + if err != nil { + return nil, err + } + return group, nil +} diff --git a/models/rbacmodel/group_test.go b/models/rbacmodel/group_test.go new file mode 100644 index 00000000..1e101830 --- /dev/null +++ b/models/rbacmodel/group_test.go @@ -0,0 +1,85 @@ +package rbacmodel_test + +import ( + "context" + "testing" + "time" + + "github.com/jiaozifs/jiaozifs/models/rbacmodel" + "github.com/jiaozifs/jiaozifs/utils" + + "github.com/brianvoe/gofakeit/v6" + "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/stretchr/testify/require" + + "github.com/google/go-cmp/cmp" + "github.com/google/uuid" +) + +func TestGroupRepo(t *testing.T) { + ctx := context.Background() + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() + + groupRepo := rbacmodel.NewGroupRepo(db) + userGroupRepo := rbacmodel.NewUserGroupRepo(db) + + t.Run("insert and get ", func(t *testing.T) { + groupModel := &rbacmodel.Group{} + require.NoError(t, gofakeit.Struct(groupModel)) + + newGroupModel, err := groupRepo.Insert(ctx, groupModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newGroupModel.ID) + + getMRParams := rbacmodel.NewGetGroupParams().SetID(newGroupModel.ID) + actualMember, err := groupRepo.Get(ctx, getMRParams) + require.NoError(t, err) + + require.True(t, cmp.Equal(actualMember, newGroupModel, testhelper.DBTimeCmpOpt)) + }) + + t.Run("insert and get by name ", func(t *testing.T) { + groupModel := &rbacmodel.Group{} + require.NoError(t, gofakeit.Struct(groupModel)) + + newGrouppModel, err := groupRepo.Insert(ctx, groupModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newGrouppModel.ID) + + userID := uuid.New() + _, err = userGroupRepo.Insert(ctx, &rbacmodel.UserGroup{ + UserID: userID, + GroupID: newGrouppModel.ID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + require.NoError(t, err) + + actualGroup, err := groupRepo.GetGroupByUserID(ctx, userID) + require.NoError(t, err) + + require.True(t, cmp.Equal(actualGroup, newGrouppModel, testhelper.DBTimeCmpOpt)) + }) + + t.Run("list", func(t *testing.T) { + var groups []*rbacmodel.Group + var names []string + for i := 0; i < 10; i++ { + groupModel := &rbacmodel.Group{} + require.NoError(t, gofakeit.Struct(groupModel)) + groupModel.CreatedAt = time.Now() + + newGroupModel, err := groupRepo.Insert(ctx, groupModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newGroupModel.ID) + groups = append(groups, newGroupModel) + names = append(names, newGroupModel.Name) + } + + listGroupParmas := rbacmodel.NewListGroupParams().SetNames(names...) + listGroups, err := groupRepo.List(ctx, listGroupParmas) + require.NoError(t, err) + require.True(t, cmp.Equal(utils.Reverse(listGroups), groups, testhelper.DBTimeCmpOpt)) + }) +} diff --git a/models/rbacmodel/policies.go b/models/rbacmodel/policies.go new file mode 100644 index 00000000..dbe4eae7 --- /dev/null +++ b/models/rbacmodel/policies.go @@ -0,0 +1,112 @@ +package rbacmodel + +import ( + "context" + "time" + + "github.com/google/uuid" + "github.com/uptrace/bun" +) + +type Statements []Statement + +type Statement struct { + Effect string `json:"effect"` + Action []string `json:"action"` + Resource Resource `json:"resource"` +} + +type Policy struct { + bun.BaseModel `bun:"table:policies"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()" json:"id"` + // Name policy name + Name string `bun:"name,unique,notnull" json:"name"` + // Actions + Statements []Statement `bun:"statements,type:jsonb,notnull" json:"statements"` + // CreatedAt + CreatedAt time.Time `bun:"created_at,type:timestamp,notnull" json:"created_at"` + // UpdatedAt + UpdatedAt time.Time `bun:"updated_at,type:timestamp,notnull" json:"updated_at"` +} + +type GetPolicyParams struct { + id uuid.UUID +} + +func NewGetPolicyParams() *GetPolicyParams { + return &GetPolicyParams{} +} + +func (gup *GetPolicyParams) SetID(id uuid.UUID) *GetPolicyParams { + gup.id = id + return gup +} + +type ListPolicyParams struct { + ids []uuid.UUID +} + +func NewListPolicyParams() *ListPolicyParams { + return &ListPolicyParams{} +} + +func (gup *ListPolicyParams) SetIDs(ids ...uuid.UUID) *ListPolicyParams { + gup.ids = ids + return gup +} + +type IPolicyRepo interface { + Get(ctx context.Context, params *GetPolicyParams) (*Policy, error) + List(ctx context.Context, params *ListPolicyParams) ([]*Policy, error) + Insert(ctx context.Context, policy *Policy) (*Policy, error) +} + +var _ IPolicyRepo = (*PolicyRepo)(nil) + +type PolicyRepo struct { + db bun.IDB +} + +func NewPolicyRepo(db bun.IDB) IPolicyRepo { + return &PolicyRepo{db: db} +} + +func (p PolicyRepo) Insert(ctx context.Context, policy *Policy) (*Policy, error) { + _, err := p.db.NewInsert().Model(policy).Exec(ctx) + if err != nil { + return nil, err + } + return policy, nil +} + +func (p PolicyRepo) Get(ctx context.Context, params *GetPolicyParams) (*Policy, error) { + ug := &Policy{} + query := p.db.NewSelect().Model(ug) + + if uuid.Nil != params.id { + query = query.Where("id = ?", params.id) + } + + err := query.Limit(1).Scan(ctx) + if err != nil { + return nil, err + } + return ug, nil +} + +func (p PolicyRepo) List(ctx context.Context, params *ListPolicyParams) ([]*Policy, error) { + var policies []*Policy + query := p.db.NewSelect().Model(&policies) + + if len(params.ids) > 0 { + query = query.Where("id IN (?)", bun.In(params.ids)) + } + + query = query.Order("created_at DESC") + + err := query.Scan(ctx) + if err != nil { + return nil, err + } + return policies, nil +} diff --git a/models/rbacmodel/policies_test.go b/models/rbacmodel/policies_test.go new file mode 100644 index 00000000..4ff92099 --- /dev/null +++ b/models/rbacmodel/policies_test.go @@ -0,0 +1,59 @@ +package rbacmodel_test + +import ( + "context" + "sort" + "testing" + + "github.com/brianvoe/gofakeit/v6" + "github.com/google/go-cmp/cmp" + "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/models/rbacmodel" + "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/stretchr/testify/require" +) + +func TestPolicyRepo(t *testing.T) { + ctx := context.Background() + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() + + policyRepo := rbacmodel.NewPolicyRepo(db) + + t.Run("insert and get ", func(t *testing.T) { + policyModel := &rbacmodel.Policy{} + require.NoError(t, gofakeit.Struct(policyModel)) + + newPolicyModel, err := policyRepo.Insert(ctx, policyModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newPolicyModel.ID) + + getPloicyParams := rbacmodel.NewGetPolicyParams().SetID(newPolicyModel.ID) + actualPolicy, err := policyRepo.Get(ctx, getPloicyParams) + require.NoError(t, err) + require.True(t, cmp.Equal(actualPolicy, newPolicyModel, testhelper.DBTimeCmpOpt)) + }) + + t.Run("list", func(t *testing.T) { + var ids []uuid.UUID + var policies []*rbacmodel.Policy + for i := 0; i < 10; i++ { + policyModel := &rbacmodel.Policy{} + require.NoError(t, gofakeit.Struct(policyModel)) + + newPolicyModel, err := policyRepo.Insert(ctx, policyModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newPolicyModel.ID) + ids = append(ids, newPolicyModel.ID) + policies = append(policies, newPolicyModel) + } + + actualPolicies, err := policyRepo.List(ctx, rbacmodel.NewListPolicyParams().SetIDs(ids...)) + require.NoError(t, err) + + sort.Slice(policies, func(i, j int) bool { + return policies[i].CreatedAt.Sub(policies[j].CreatedAt) > 0 + }) + require.True(t, cmp.Equal(actualPolicies, policies, testhelper.DBTimeCmpOpt)) + }) +} diff --git a/models/rbacmodel/resoure.go b/models/rbacmodel/resoure.go new file mode 100644 index 00000000..b08aa2b1 --- /dev/null +++ b/models/rbacmodel/resoure.go @@ -0,0 +1,63 @@ +package rbacmodel + +import ( + "fmt" + "strings" +) + +type ResourceType string + +const ( + RepoRT ResourceType = "repo" + UserRT ResourceType = "user" + AuthRT ResourceType = "auth" +) + +type Resource string + +const ( + // this is arn with s3, maybe we dont need this https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html + repoArnPrefix Resource = "arn:gitdata:jiaozifs:::" + authArnPrefix Resource = "arn:gitdata:jiaozifs:::" + userArnPrefix Resource = "arn:gitdata:jiaozifs:::" + All Resource = "*" + + UserIDCapture = "{user_id}" + RepoIDCapture = "{repo_id}" +) + +func (r Resource) WithRepoID(repoID string) Resource { + return Resource(strings.ReplaceAll(string(r), RepoIDCapture, repoID)) +} + +func (r Resource) WithUserID(userID string) Resource { + return Resource(strings.ReplaceAll(string(r), UserIDCapture, userID)) +} + +func (r Resource) String() string { + return string(r) +} +func RepoURArn(userID string, repoID string) Resource { + return Resource(fmt.Sprintf("%srepository/%s/%s", repoArnPrefix, userID, repoID)) +} + +// RepoUArn anything in this repo +func RepoUArn(userID string) Resource { + return Resource(fmt.Sprintf("%srepository/%s/*", repoArnPrefix, userID)) +} + +func UserArn(userID string) Resource { + return Resource(fmt.Sprintf("%suser/%s", userArnPrefix, userID)) +} + +func UserAkskArn(userID string) Resource { + return Resource(fmt.Sprintf("%suser/aksk/%s", userArnPrefix, userID)) +} + +func GroupArn(groupID string) Resource { + return Resource(fmt.Sprintf("%sgroup/%s", authArnPrefix, groupID)) +} + +func PolicyArn(policyID string) Resource { + return Resource(fmt.Sprintf("%spolicy/%s", authArnPrefix, policyID)) +} diff --git a/models/rbacmodel/user_group.go b/models/rbacmodel/user_group.go new file mode 100644 index 00000000..b7b51855 --- /dev/null +++ b/models/rbacmodel/user_group.go @@ -0,0 +1,81 @@ +package rbacmodel + +import ( + "context" + "time" + + "github.com/google/uuid" + "github.com/uptrace/bun" +) + +type UserGroup struct { + bun.BaseModel `bun:"table:usergroup"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()" json:"id"` + UserID uuid.UUID `bun:"user_id,type:uuid,unique:user_group_pk,notnull" json:"user_id"` + GroupID uuid.UUID `bun:"group_id,type:uuid,unique:user_group_pk,notnull" json:"group_id"` + // CreatedAt + CreatedAt time.Time `bun:"created_at,type:timestamp,notnull" json:"created_at"` + // UpdatedAt + UpdatedAt time.Time `bun:"updated_at,type:timestamp,notnull" json:"updated_at"` +} + +type GetUserGroupParams struct { + userID uuid.UUID + groupID uuid.UUID +} + +func NewGetUserGroupParams() *GetUserGroupParams { + return &GetUserGroupParams{} +} + +func (gup *GetUserGroupParams) SetUserID(userID uuid.UUID) *GetUserGroupParams { + gup.userID = userID + return gup +} + +func (gup *GetUserGroupParams) SetGroupID(groupID uuid.UUID) *GetUserGroupParams { + gup.groupID = groupID + return gup +} + +type IUserGroupRepo interface { + Get(ctx context.Context, params *GetUserGroupParams) (*UserGroup, error) + Insert(ctx context.Context, asSk *UserGroup) (*UserGroup, error) +} + +var _ IUserGroupRepo = (*UserGroupRepo)(nil) + +type UserGroupRepo struct { + db bun.IDB +} + +func NewUserGroupRepo(db bun.IDB) IUserGroupRepo { + return &UserGroupRepo{db: db} +} + +func (a UserGroupRepo) Insert(ctx context.Context, group *UserGroup) (*UserGroup, error) { + _, err := a.db.NewInsert().Model(group).Exec(ctx) + if err != nil { + return nil, err + } + return group, nil +} + +func (a UserGroupRepo) Get(ctx context.Context, params *GetUserGroupParams) (*UserGroup, error) { + ug := &UserGroup{} + query := a.db.NewSelect().Model(ug) + + if uuid.Nil != params.userID { + query = query.Where("user_id = ?", params.userID) + } + + if uuid.Nil != params.groupID { + query = query.Where("group_id = ?", params.groupID) + } + + err := query.Limit(1).Scan(ctx) + if err != nil { + return nil, err + } + return ug, nil +} diff --git a/models/rbacmodel/user_group_test.go b/models/rbacmodel/user_group_test.go new file mode 100644 index 00000000..1ef9d78e --- /dev/null +++ b/models/rbacmodel/user_group_test.go @@ -0,0 +1,39 @@ +package rbacmodel_test + +import ( + "context" + "testing" + + "github.com/brianvoe/gofakeit/v6" + "github.com/google/go-cmp/cmp" + "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/models/rbacmodel" + "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/stretchr/testify/require" +) + +func TestUserGroupRepo(t *testing.T) { + ctx := context.Background() + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() + + userGroupRepo := rbacmodel.NewUserGroupRepo(db) + + t.Run("insert and get ", func(t *testing.T) { + userGroupModel := &rbacmodel.UserGroup{} + require.NoError(t, gofakeit.Struct(userGroupModel)) + + newUserGroup, err := userGroupRepo.Insert(ctx, userGroupModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, newUserGroup.ID) + + getUserGroupParams := rbacmodel.NewGetUserGroupParams().SetUserID(userGroupModel.UserID).SetGroupID(userGroupModel.GroupID) + actualUserGroup, err := userGroupRepo.Get(ctx, getUserGroupParams) + require.NoError(t, err) + require.True(t, cmp.Equal(actualUserGroup, newUserGroup, testhelper.DBTimeCmpOpt)) + + _, err = userGroupRepo.Insert(ctx, userGroupModel) + require.Error(t, err) + }) + +} diff --git a/models/repo.go b/models/repo.go index 2a88194e..899d9145 100644 --- a/models/repo.go +++ b/models/repo.go @@ -5,6 +5,7 @@ import ( "database/sql" "github.com/google/uuid" + "github.com/jiaozifs/jiaozifs/models/rbacmodel" "github.com/uptrace/bun" ) @@ -36,6 +37,11 @@ type IRepo interface { RepositoryRepo() IRepositoryRepo WipRepo() IWipRepo AkskRepo() IAkskRepo + + MemberRepo() IMemberRepo + GroupRepo() rbacmodel.IGroupRepo + PolicyRepo() rbacmodel.IPolicyRepo + UserGroupRepo() rbacmodel.IUserGroupRepo } type PgRepo struct { @@ -97,3 +103,19 @@ func (repo *PgRepo) WipRepo() IWipRepo { func (repo *PgRepo) AkskRepo() IAkskRepo { return NewAkskRepo(repo.db) } + +func (repo *PgRepo) MemberRepo() IMemberRepo { + return NewMemberRepo(repo.db) +} + +func (repo *PgRepo) GroupRepo() rbacmodel.IGroupRepo { + return rbacmodel.NewGroupRepo(repo.db) +} + +func (repo *PgRepo) PolicyRepo() rbacmodel.IPolicyRepo { + return rbacmodel.NewPolicyRepo(repo.db) +} + +func (repo *PgRepo) UserGroupRepo() rbacmodel.IUserGroupRepo { + return rbacmodel.NewUserGroupRepo(repo.db) +} diff --git a/models/repository_test.go b/models/repository_test.go index 810ed297..b4948cef 100644 --- a/models/repository_test.go +++ b/models/repository_test.go @@ -63,7 +63,7 @@ func TestRepositoryRepoInsert(t *testing.T) { user, err := repo.Get(ctx, models.NewGetRepoParams().SetID(newRepo.ID)) require.NoError(t, err) - require.True(t, cmp.Equal(repoModel, user, dbTimeCmpOpt)) + require.True(t, cmp.Equal(repoModel, user, testhelper.DBTimeCmpOpt)) //insert secondary secModel := &models.Repository{} diff --git a/models/tag_test.go b/models/tag_test.go index b18938f3..3f95f7d4 100644 --- a/models/tag_test.go +++ b/models/tag_test.go @@ -29,7 +29,7 @@ func TestTagRepo(t *testing.T) { tagModel, err = tagRepo.Tag(ctx, tagModel.Hash) require.NoError(t, err) - require.True(t, cmp.Equal(tagModel, newTagModel, dbTimeCmpOpt)) + require.True(t, cmp.Equal(tagModel, newTagModel, testhelper.DBTimeCmpOpt)) t.Run("mis match repo id", func(t *testing.T) { mistMatchModel := &models.Tag{} diff --git a/models/tree_test.go b/models/tree_test.go index cd5d7d9a..08223ef0 100644 --- a/models/tree_test.go +++ b/models/tree_test.go @@ -65,7 +65,7 @@ func TestObjectRepo_Insert(t *testing.T) { ref, err := repo.Get(ctx, models.NewGetObjParams().SetHash(newObj.Hash)) require.NoError(t, err) - require.True(t, cmp.Equal(newObj, ref, dbTimeCmpOpt)) + require.True(t, cmp.Equal(newObj, ref, testhelper.DBTimeCmpOpt)) t.Run("mis match repo id", func(t *testing.T) { mistMatchModel := &models.FileTree{} require.NoError(t, gofakeit.Struct(mistMatchModel)) diff --git a/models/user_test.go b/models/user_test.go index 95c2201e..27105dbe 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -3,7 +3,6 @@ package models_test import ( "context" "testing" - "time" "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" @@ -13,10 +12,6 @@ import ( "github.com/stretchr/testify/require" ) -var dbTimeCmpOpt = cmp.Comparer(func(x, y time.Time) bool { - return x.Unix() == y.Unix() -}) - func TestNewUserRepo(t *testing.T) { ctx := context.Background() closeDB, _, db := testhelper.SetupDatabase(ctx, t) @@ -33,7 +28,7 @@ func TestNewUserRepo(t *testing.T) { user, err := repo.Get(ctx, models.NewGetUserParams().SetID(newUser.ID)) require.NoError(t, err) - require.True(t, cmp.Equal(userModel, user, dbTimeCmpOpt)) + require.True(t, cmp.Equal(userModel, user, testhelper.DBTimeCmpOpt)) ep, err := repo.GetEPByName(ctx, newUser.Name) require.NoError(t, err) @@ -41,11 +36,11 @@ func TestNewUserRepo(t *testing.T) { userByEmail, err := repo.Get(ctx, models.NewGetUserParams().SetEmail(newUser.Email)) require.NoError(t, err) - require.True(t, cmp.Equal(userModel, userByEmail, dbTimeCmpOpt)) + require.True(t, cmp.Equal(userModel, userByEmail, testhelper.DBTimeCmpOpt)) userByName, err := repo.Get(ctx, models.NewGetUserParams().SetName(newUser.Name)) require.NoError(t, err) - require.True(t, cmp.Equal(userModel, userByName, dbTimeCmpOpt)) + require.True(t, cmp.Equal(userModel, userByName, testhelper.DBTimeCmpOpt)) } func TestCount(t *testing.T) { diff --git a/models/wip_test.go b/models/wip_test.go index 0640ed56..86eb1c78 100644 --- a/models/wip_test.go +++ b/models/wip_test.go @@ -33,7 +33,7 @@ func TestWipRepo(t *testing.T) { SetRefID(newWipModel.RefID) user, err := repo.Get(ctx, getWipParams) require.NoError(t, err) - require.True(t, cmp.Equal(newWipModel, user, dbTimeCmpOpt)) + require.True(t, cmp.Equal(newWipModel, user, testhelper.DBTimeCmpOpt)) t.Run("list", func(t *testing.T) { secWipModel := &models.WorkingInProcess{} diff --git a/testhelper/tf.go b/testhelper/tf.go new file mode 100644 index 00000000..a5daf16c --- /dev/null +++ b/testhelper/tf.go @@ -0,0 +1,11 @@ +package testhelper + +import ( + "time" + + "github.com/google/go-cmp/cmp" +) + +var DBTimeCmpOpt = cmp.Comparer(func(x, y time.Time) bool { + return x.Unix() == y.Unix() +}) diff --git a/utils/arr.go b/utils/arr.go index 1878af85..e3e60326 100644 --- a/utils/arr.go +++ b/utils/arr.go @@ -14,3 +14,22 @@ func Contain[T interface { } return false } + +func Reverse[T any](items []T) []T { + for i, j := 0, len(items)-1; i < j; i, j = i+1, j-1 { + items[i], items[j] = items[j], items[i] + } + return items +} + +func ArrMap[T any, T2 any](items []T, mapFn func(T) (T2, error)) ([]T2, error) { + newItems := make([]T2, len(items)) + for index, item := range items { + newItem, err := mapFn(item) + if err != nil { + return nil, err + } + newItems[index] = newItem + } + return newItems, nil +} diff --git a/utils/arr_test.go b/utils/arr_test.go index d5506062..9b9c5b4a 100644 --- a/utils/arr_test.go +++ b/utils/arr_test.go @@ -1,6 +1,8 @@ package utils import ( + "errors" + "strconv" "testing" "github.com/stretchr/testify/require" @@ -28,3 +30,35 @@ func TestContain(t *testing.T) { require.False(t, Contain([]bool{true, true}, false)) }) } + +func TestReverse(t *testing.T) { + t.Run("empty", func(t *testing.T) { + require.Len(t, Reverse([]string{}), 0) + }) + + t.Run("reverse", func(t *testing.T) { + require.Equal(t, Reverse([]int{1, 2}), []int{2, 1}) + }) + + t.Run("reverse", func(t *testing.T) { + require.Equal(t, Reverse([]int{1, 2, 3, 4, 5}), []int{5, 4, 3, 2, 1}) + }) +} + +func TestArrMap(t *testing.T) { + t.Run("success", func(t *testing.T) { + result, err := ArrMap[int, string]([]int{1, 2, 3}, func(i int) (string, error) { + return strconv.Itoa(i), nil + }) + require.NoError(t, err) + require.Equal(t, []string{"1", "2", "3"}, result) + }) + + t.Run("fail", func(t *testing.T) { + var stopErr = errors.New("mock error") + _, gotErr := ArrMap[int, string]([]int{1, 2, 3}, func(i int) (string, error) { + return strconv.Itoa(i), stopErr + }) + require.Equal(t, stopErr, gotErr) + }) +} diff --git a/utils/error.go b/utils/error.go new file mode 100644 index 00000000..57bca99b --- /dev/null +++ b/utils/error.go @@ -0,0 +1,13 @@ +package utils + +func Silent[T any](val T, _ error) T { + return val +} + +func Silent2[T any, T2 any](val1 T, val2 T2, _ error) (T, T2) { + return val1, val2 +} + +func Silent3[T any, T2 any, T3 any](val1 T, val2 T2, val3 T3, _ error) (T, T2, T3) { + return val1, val2, val3 +} diff --git a/versionmgr/work_repo.go b/versionmgr/work_repo.go index 8f30a444..656c9291 100644 --- a/versionmgr/work_repo.go +++ b/versionmgr/work_repo.go @@ -617,18 +617,6 @@ func (repository *WorkRepository) GetCommitChanges(ctx context.Context, pathPref if err != nil { return nil, err } - files, _ := workTree.Ls(ctx, "g") - fmt.Println(files) - { - workTree2, err := NewWorkTree(ctx, repository.repo.FileTreeRepo(repository.repoModel.ID), models.NewRootTreeEntry(repository.commit.TreeHash)) - if err != nil { - return nil, err - } - f2, _ := workTree2.Ls(ctx, "g") - fmt.Println(f2) - } - fmt.Println(repository.commit.Hash.Hex()) - fmt.Println(commitHash.Hex()) return workTree.Diff(ctx, repository.commit.TreeHash, pathPrefix) } From a57df6d85fbdc494caefffb3ec44eda9b08a9a57 Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Mon, 4 Mar 2024 13:08:29 +0800 Subject: [PATCH 187/210] fix: return safe aksk (#131) --- api/jiaozifs.gen.go | 183 ++++++++++++++++++----------------- api/swagger.yml | 25 ++++- controller/aksk_ctl.go | 14 ++- integrationtest/aksk_test.go | 6 +- 4 files changed, 133 insertions(+), 95 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 7825cd71..c234a493 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -73,7 +73,7 @@ type Aksk struct { // AkskList defines model for AkskList. type AkskList struct { Pagination Pagination `json:"pagination"` - Results []Aksk `json:"results"` + Results []SafeAksk `json:"results"` } // AuthenticationToken defines model for AuthenticationToken. @@ -320,6 +320,15 @@ type RepositoryList struct { Results []Repository `json:"results"` } +// SafeAksk defines model for SafeAksk. +type SafeAksk struct { + AccessKey string `json:"access_key"` + CreatedAt int64 `json:"created_at"` + Description *string `json:"description,omitempty"` + Id openapi_types.UUID `json:"id"` + UpdatedAt int64 `json:"updated_at"` +} + // SetupState defines model for SetupState. type SetupState struct { // CommPrefsMissing true if the comm prefs are missing. @@ -5205,7 +5214,7 @@ func (r DeleteAkskResponse) StatusCode() int { type GetAkskResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *Aksk + JSON200 *SafeAksk } // Status returns HTTPResponse.Status @@ -6708,7 +6717,7 @@ func ParseGetAkskResponse(rsp *http.Response) (*GetAkskResponse, error) { switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest Aksk + var dest SafeAksk if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -10663,90 +10672,90 @@ var swaggerSpec = []string{ "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", - "lIyzoGGuSKYOd3fpx7xS966CG81i4DuurlCUondoqBGG4X3tpa53OER2V4r2KpqCHkfNT51WUGWoXwtS", - "JtaWtTQBiV+iir1DqOR5z5eyHvP61PgESl8lzsdwfbxJjCvfvsef3PkN/tYw93y2018ugsLy0cykdR/X", - "FQIw9q7+7YvPFCC5Q0vpA+LT0lXQ9fAiA2em+UDp3XmHRajZD1c/d2tq6sf6m9BzCPeI/rwY78PWPM48", - "BmO/AeoIQK3ksZfO/yMBVP2Fwvz6QH44cC9XmnRuS6W2f5epl2X9dzaF9a+kuAqrNr5V0uff2DsY+SuY", - "hsjxJRVX6HRJ0g0rPv1GUm+zykxL8sCFtjkk7ArQkvFLQudKHVPOtF9bSkkR2Rdm7GZ/K+qhuncohYPk", - "B6/HNEiM6615q6d7Gx05ti6cDrtkurVs9lyldnVYXujU5mfk7bnerM7Hjio+Fd/1rOheH8pNKh/37jH0", - "zqICO9eYodfhrPY/h+upDhXLZ+kRQh0qv709HvIeQ6Zvt2kU3y17evVp7xO7TRKEwe5eeLCXUhMQwnxS", - "1kVaIuZ3rER2uGsfxPKR+3Xa2bQkoCWRC0Rh+SA+nu9956rAP6hes+HJYd+Stcs4DVhYYrImO34L/uMg", - "4P2NpJuh7iPYMi5JWtsrppzpMr9K5RoRhmcCuhyugA8E3f8Ah9lvf8ATInKNWITWeDw24rMRop/oSaj5", - "faO2uWYSHwwCHcPZoF4R5HNF9yzVlWpLbUzwEShHVAvffEDJvoXjuI2Pa8/uqp+tdJ/m+TfOT2Dq8Efl", - "o9u1P+33HesP84iOflp+eTE/MdTycS3alQQ0s3jQMGXmflD5XcWjySRmAY4XTMij12/+evh6glMyuTr0", - "2hq8tsPi1Yvb/wsAAP//tmwzG1CjAAA=", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 619c2fc4..9be8380e 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -208,7 +208,7 @@ components: results: type: array items: - $ref: "#/components/schemas/Aksk" + $ref: "#/components/schemas/SafeAksk" Aksk: type: object required: @@ -233,6 +233,27 @@ components: updated_at: type: integer format: int64 + SafeAksk: + type: object + required: + - id + - access_key + - created_at + - updated_at + properties: + id: + type: string + format: uuid + access_key: + type: string + description: + type: string + created_at: + type: integer + format: int64 + updated_at: + type: integer + format: int64 BranchCreation: type: object required: @@ -2451,7 +2472,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Aksk" + $ref: "#/components/schemas/SafeAksk" 401: description: Unauthorized 404: diff --git a/controller/aksk_ctl.go b/controller/aksk_ctl.go index e674fe42..154031a0 100644 --- a/controller/aksk_ctl.go +++ b/controller/aksk_ctl.go @@ -95,7 +95,7 @@ func (akskCtl AkSkController) GetAksk(ctx context.Context, w *api.JiaozifsRespon w.Error(err) return } - w.JSON(utils.Silent(akskToDto(aksk))) + w.JSON(utils.Silent(akskToSafeDto(aksk))) } func (akskCtl AkSkController) DeleteAksk(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, params api.DeleteAkskParams) { @@ -161,7 +161,7 @@ func (akskCtl AkSkController) ListAksks(ctx context.Context, w *api.JiaozifsResp w.Error(err) return } - results := utils.Silent(utils.ArrMap(aksks, akskToDto)) + results := utils.Silent(utils.ArrMap(aksks, akskToSafeDto)) pagMag := utils.PaginationFor(hasMore, results, "UpdatedAt") pagination := api.Pagination{ HasMore: pagMag.HasMore, @@ -185,3 +185,13 @@ func akskToDto(in *models.AkSk) (api.Aksk, error) { UpdatedAt: in.UpdatedAt.UnixMilli(), }, nil } + +func akskToSafeDto(in *models.AkSk) (api.SafeAksk, error) { + return api.SafeAksk{ + AccessKey: in.AccessKey, + CreatedAt: in.CreatedAt.UnixMilli(), + Description: in.Description, + Id: in.ID, + UpdatedAt: in.UpdatedAt.UnixMilli(), + }, nil +} diff --git a/integrationtest/aksk_test.go b/integrationtest/aksk_test.go index f8aeece7..e69dcebf 100644 --- a/integrationtest/aksk_test.go +++ b/integrationtest/aksk_test.go @@ -61,7 +61,6 @@ func AkSkSpec(ctx context.Context, urlStr string) func(c convey.C) { akskResult, err := api.ParseGetAkskResponse(resp) convey.So(err, convey.ShouldBeNil) convey.ShouldEqual(akskResult.JSON200.AccessKey, aksk.AccessKey) - convey.ShouldEqual(akskResult.JSON200.SecretKey, aksk.SecretKey) convey.ShouldEqual(akskResult.JSON200.Description, aksk.Description) }) @@ -73,7 +72,6 @@ func AkSkSpec(ctx context.Context, urlStr string) func(c convey.C) { akskResult, err := api.ParseGetAkskResponse(resp) convey.So(err, convey.ShouldBeNil) convey.ShouldEqual(akskResult.JSON200.Id, aksk.Id) - convey.ShouldEqual(akskResult.JSON200.SecretKey, aksk.SecretKey) convey.ShouldEqual(akskResult.JSON200.Description, aksk.Description) }) c.Convey("get nothing by ak", func() { @@ -134,7 +132,7 @@ func AkSkSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) - c.Convey("delete nothing by ak", func() { + c.Convey("list aksk success", func() { resp, err := client.ListAksks(ctx, &api.ListAksksParams{}) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) @@ -145,7 +143,7 @@ func AkSkSpec(ctx context.Context, urlStr string) func(c convey.C) { }) }) - c.Convey("aksk usage", func(c convey.C) { + c.Convey("aksk user", func(c convey.C) { c.Convey("success", func(c convey.C) { aksk, err := createAksk(ctx, client) convey.So(err, convey.ShouldBeNil) From d42efd8ebc2964a281e6e0df50292fb0800571d4 Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Mon, 4 Mar 2024 17:08:00 +0800 Subject: [PATCH 188/210] feat: support public and private repository (#132) * fix: return safe aksk (#131) * test: add test * feat: add api for query public repo --- README.md | 4 +- api/jiaozifs.gen.go | 637 ++++++++++++++++++++---- api/swagger.yml | 107 +++- auth/rbac/rbac.go | 77 ++- auth/rbac/rbac_test.go | 62 ++- controller/{group.go => group_ctl.go} | 0 controller/{member.go => member_ctl.go} | 0 controller/repository_ctl.go | 69 +++ integrationtest/aksk_test.go | 22 +- integrationtest/branch_test.go | 5 +- integrationtest/commit_test.go | 16 +- integrationtest/group_test.go | 2 +- integrationtest/helper_test.go | 51 +- integrationtest/member_test.go | 58 +-- integrationtest/merge_request_test.go | 12 +- integrationtest/objects_test.go | 8 +- integrationtest/public_repo_test.go | 156 ++++++ integrationtest/repo_test.go | 4 +- integrationtest/root_test.go | 1 + integrationtest/user_test.go | 2 +- integrationtest/wip_object_test.go | 24 +- integrationtest/wip_test.go | 12 +- models/rbacmodel/actions.gen.go | 1 + models/rbacmodel/actions.go | 2 + models/rbacmodel/actions_test.go | 8 + models/repository.go | 37 +- models/repository_test.go | 32 +- 27 files changed, 1140 insertions(+), 269 deletions(-) rename controller/{group.go => group_ctl.go} (100%) rename controller/{member.go => member_ctl.go} (100%) create mode 100644 integrationtest/public_repo_test.go diff --git a/README.md b/README.md index 91250a1c..ac9004a4 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,14 @@ A version control file system for data centric applications & teams.

- +

### Basic Build And Usage #### Requirement -1. To build JiaoziFS, you need a working installation of [Go 1.20.10 or higher](https://golang.org/dl/) +1. To build JiaoziFS, you need a working installation of [Go 1.22.0 or higher](https://golang.org/dl/) 2. JiaoziFS use postgres to store running data, you can install at [postgres install installation guide](https://www.postgresql.org/docs/current/installation.html) #### Build And Running diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index c234a493..4c615913 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -156,6 +156,7 @@ type CreateRepository struct { BlockstoreConfig *string `json:"blockstore_config,omitempty"` Description *string `json:"description,omitempty"` Name string `json:"name"` + Visible *bool `json:"visible,omitempty"` } // FullTreeEntry defines model for FullTreeEntry. @@ -312,6 +313,7 @@ type Repository struct { StorageNamespace *string `json:"storage_namespace,omitempty"` UpdatedAt int64 `json:"updated_at"` UsePublicStorage bool `json:"use_public_storage"` + Visible bool `json:"visible"` } // RepositoryList defines model for RepositoryList. @@ -481,6 +483,18 @@ type UploadObjectParams struct { Path string `form:"path" json:"path"` } +// ListPublicRepositoryParams defines parameters for ListPublicRepository. +type ListPublicRepositoryParams struct { + // Prefix return items prefixed with this value + Prefix *PaginationPrefix `form:"prefix,omitempty" json:"prefix,omitempty"` + + // After return items after this value + After *PaginationInt64After `form:"after,omitempty" json:"after,omitempty"` + + // Amount how many items to return + Amount *PaginationAmount `form:"amount,omitempty" json:"amount,omitempty"` +} + // DeleteRepositoryParams defines parameters for DeleteRepository. type DeleteRepositoryParams struct { IsCleanData *bool `form:"is_clean_data,omitempty" json:"is_clean_data,omitempty"` @@ -571,6 +585,11 @@ type ListMergeRequestsParams struct { State *int `form:"state,omitempty" json:"state,omitempty"` } +// ChangeVisibleParams defines parameters for ChangeVisible. +type ChangeVisibleParams struct { + Visible bool `form:"visible" json:"visible"` +} + // DeleteAkskParams defines parameters for DeleteAksk. type DeleteAkskParams struct { Id *openapi_types.UUID `form:"id,omitempty" json:"id,omitempty"` @@ -792,6 +811,9 @@ type ClientInterface interface { // UploadObjectWithBody request with any body UploadObjectWithBody(ctx context.Context, owner string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + // ListPublicRepository request + ListPublicRepository(ctx context.Context, params *ListPublicRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // DeleteRepository request DeleteRepository(ctx context.Context, owner string, repository string, params *DeleteRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -862,6 +884,9 @@ type ClientInterface interface { Merge(ctx context.Context, owner string, repository string, mrSeq uint64, body MergeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // ChangeVisible request + ChangeVisible(ctx context.Context, owner string, repository string, params *ChangeVisibleParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetSetupState request GetSetupState(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -1022,6 +1047,18 @@ func (c *Client) UploadObjectWithBody(ctx context.Context, owner string, reposit return c.Client.Do(req) } +func (c *Client) ListPublicRepository(ctx context.Context, params *ListPublicRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListPublicRepositoryRequest(c.Server, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) DeleteRepository(ctx context.Context, owner string, repository string, params *DeleteRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewDeleteRepositoryRequest(c.Server, owner, repository, params) if err != nil { @@ -1322,6 +1359,18 @@ func (c *Client) Merge(ctx context.Context, owner string, repository string, mrS return c.Client.Do(req) } +func (c *Client) ChangeVisible(ctx context.Context, owner string, repository string, params *ChangeVisibleParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewChangeVisibleRequest(c.Server, owner, repository, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) GetSetupState(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewGetSetupStateRequest(c.Server) if err != nil { @@ -2020,6 +2069,87 @@ func NewUploadObjectRequestWithBody(server string, owner string, repository stri return req, nil } +// NewListPublicRepositoryRequest generates requests for ListPublicRepository +func NewListPublicRepositoryRequest(server string, params *ListPublicRepositoryParams) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/repos/public") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Prefix != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "prefix", runtime.ParamLocationQuery, *params.Prefix); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.After != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "after", runtime.ParamLocationQuery, *params.After); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Amount != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "amount", runtime.ParamLocationQuery, *params.Amount); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewDeleteRepositoryRequest generates requests for DeleteRepository func NewDeleteRepositoryRequest(server string, owner string, repository string, params *DeleteRepositoryParams) (*http.Request, error) { var err error @@ -3332,6 +3462,65 @@ func NewMergeRequestWithBody(server string, owner string, repository string, mrS return req, nil } +// NewChangeVisibleRequest generates requests for ChangeVisible +func NewChangeVisibleRequest(server string, owner string, repository string, params *ChangeVisibleParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/repos/%s/%s/visible", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "visible", runtime.ParamLocationQuery, params.Visible); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("POST", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewGetSetupStateRequest generates requests for GetSetupState func NewGetSetupStateRequest(server string) (*http.Request, error) { var err error @@ -4451,6 +4640,9 @@ type ClientWithResponsesInterface interface { // UploadObjectWithBodyWithResponse request with any body UploadObjectWithBodyWithResponse(ctx context.Context, owner string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) + // ListPublicRepositoryWithResponse request + ListPublicRepositoryWithResponse(ctx context.Context, params *ListPublicRepositoryParams, reqEditors ...RequestEditorFn) (*ListPublicRepositoryResponse, error) + // DeleteRepositoryWithResponse request DeleteRepositoryWithResponse(ctx context.Context, owner string, repository string, params *DeleteRepositoryParams, reqEditors ...RequestEditorFn) (*DeleteRepositoryResponse, error) @@ -4521,6 +4713,9 @@ type ClientWithResponsesInterface interface { MergeWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, body MergeJSONRequestBody, reqEditors ...RequestEditorFn) (*MergeResponse, error) + // ChangeVisibleWithResponse request + ChangeVisibleWithResponse(ctx context.Context, owner string, repository string, params *ChangeVisibleParams, reqEditors ...RequestEditorFn) (*ChangeVisibleResponse, error) + // GetSetupStateWithResponse request GetSetupStateWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetSetupStateResponse, error) @@ -4735,6 +4930,28 @@ func (r UploadObjectResponse) StatusCode() int { return 0 } +type ListPublicRepositoryResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *RepositoryList +} + +// Status returns HTTPResponse.Status +func (r ListPublicRepositoryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ListPublicRepositoryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type DeleteRepositoryResponse struct { Body []byte HTTPResponse *http.Response @@ -4845,7 +5062,7 @@ func (r GetBranchResponse) StatusCode() int { type CreateBranchResponse struct { Body []byte HTTPResponse *http.Response - JSON201 *BranchCreation + JSON201 *Branch } // Status returns HTTPResponse.Status @@ -5168,6 +5385,27 @@ func (r MergeResponse) StatusCode() int { return 0 } +type ChangeVisibleResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r ChangeVisibleResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ChangeVisibleResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type GetSetupStateResponse struct { Body []byte HTTPResponse *http.Response @@ -5653,6 +5891,15 @@ func (c *ClientWithResponses) UploadObjectWithBodyWithResponse(ctx context.Conte return ParseUploadObjectResponse(rsp) } +// ListPublicRepositoryWithResponse request returning *ListPublicRepositoryResponse +func (c *ClientWithResponses) ListPublicRepositoryWithResponse(ctx context.Context, params *ListPublicRepositoryParams, reqEditors ...RequestEditorFn) (*ListPublicRepositoryResponse, error) { + rsp, err := c.ListPublicRepository(ctx, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseListPublicRepositoryResponse(rsp) +} + // DeleteRepositoryWithResponse request returning *DeleteRepositoryResponse func (c *ClientWithResponses) DeleteRepositoryWithResponse(ctx context.Context, owner string, repository string, params *DeleteRepositoryParams, reqEditors ...RequestEditorFn) (*DeleteRepositoryResponse, error) { rsp, err := c.DeleteRepository(ctx, owner, repository, params, reqEditors...) @@ -5873,6 +6120,15 @@ func (c *ClientWithResponses) MergeWithResponse(ctx context.Context, owner strin return ParseMergeResponse(rsp) } +// ChangeVisibleWithResponse request returning *ChangeVisibleResponse +func (c *ClientWithResponses) ChangeVisibleWithResponse(ctx context.Context, owner string, repository string, params *ChangeVisibleParams, reqEditors ...RequestEditorFn) (*ChangeVisibleResponse, error) { + rsp, err := c.ChangeVisible(ctx, owner, repository, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseChangeVisibleResponse(rsp) +} + // GetSetupStateWithResponse request returning *GetSetupStateResponse func (c *ClientWithResponses) GetSetupStateWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetSetupStateResponse, error) { rsp, err := c.GetSetupState(ctx, reqEditors...) @@ -6210,6 +6466,32 @@ func ParseUploadObjectResponse(rsp *http.Response) (*UploadObjectResponse, error return response, nil } +// ParseListPublicRepositoryResponse parses an HTTP response from a ListPublicRepositoryWithResponse call +func ParseListPublicRepositoryResponse(rsp *http.Response) (*ListPublicRepositoryResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListPublicRepositoryResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest RepositoryList + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseDeleteRepositoryResponse parses an HTTP response from a DeleteRepositoryWithResponse call func ParseDeleteRepositoryResponse(rsp *http.Response) (*DeleteRepositoryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -6325,7 +6607,7 @@ func ParseCreateBranchResponse(rsp *http.Response) (*CreateBranchResponse, error switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest BranchCreation + var dest Branch if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -6660,6 +6942,22 @@ func ParseMergeResponse(rsp *http.Response) (*MergeResponse, error) { return response, nil } +// ParseChangeVisibleResponse parses an HTTP response from a ChangeVisibleWithResponse call +func ParseChangeVisibleResponse(rsp *http.Response) (*ChangeVisibleResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ChangeVisibleResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + // ParseGetSetupStateResponse parses an HTTP response from a GetSetupStateWithResponse call func ParseGetSetupStateResponse(rsp *http.Response) (*GetSetupStateResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -7137,6 +7435,9 @@ type ServerInterface interface { // (POST /object/{owner}/{repository}) UploadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params UploadObjectParams) + // list public repository in all system + // (GET /repos/public) + ListPublicRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, params ListPublicRepositoryParams) // delete repository // (DELETE /repos/{owner}/{repository}) DeleteRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteRepositoryParams) @@ -7197,6 +7498,9 @@ type ServerInterface interface { // merge a mergerequest // (POST /repos/{owner}/{repository}/mergerequest/{mrSeq}/merge) Merge(ctx context.Context, w *JiaozifsResponse, r *http.Request, body MergeJSONRequestBody, owner string, repository string, mrSeq uint64) + // change repository visible(true for public, false for private) + // (POST /repos/{owner}/{repository}/visible) + ChangeVisible(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params ChangeVisibleParams) // check if jiaozifs setup // (GET /setup) GetSetupState(ctx context.Context, w *JiaozifsResponse, r *http.Request) @@ -7301,6 +7605,12 @@ func (_ Unimplemented) UploadObject(ctx context.Context, w *JiaozifsResponse, r w.WriteHeader(http.StatusNotImplemented) } +// list public repository in all system +// (GET /repos/public) +func (_ Unimplemented) ListPublicRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, params ListPublicRepositoryParams) { + w.WriteHeader(http.StatusNotImplemented) +} + // delete repository // (DELETE /repos/{owner}/{repository}) func (_ Unimplemented) DeleteRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteRepositoryParams) { @@ -7421,6 +7731,12 @@ func (_ Unimplemented) Merge(ctx context.Context, w *JiaozifsResponse, r *http.R w.WriteHeader(http.StatusNotImplemented) } +// change repository visible(true for public, false for private) +// (POST /repos/{owner}/{repository}/visible) +func (_ Unimplemented) ChangeVisible(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params ChangeVisibleParams) { + w.WriteHeader(http.StatusNotImplemented) +} + // check if jiaozifs setup // (GET /setup) func (_ Unimplemented) GetSetupState(ctx context.Context, w *JiaozifsResponse, r *http.Request) { @@ -8039,6 +8355,66 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R handler.ServeHTTP(w, r.WithContext(ctx)) } +// ListPublicRepository operation middleware +func (siw *ServerInterfaceWrapper) ListPublicRepository(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params ListPublicRepositoryParams + + // ------------- Optional query parameter "prefix" ------------- + + err = runtime.BindQueryParameter("form", true, false, "prefix", r.URL.Query(), ¶ms.Prefix) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "prefix", Err: err}) + return + } + + // ------------- Optional query parameter "after" ------------- + + err = runtime.BindQueryParameter("form", true, false, "after", r.URL.Query(), ¶ms.After) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "after", Err: err}) + return + } + + // ------------- Optional query parameter "amount" ------------- + + err = runtime.BindQueryParameter("form", true, false, "amount", r.URL.Query(), ¶ms.Amount) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "amount", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListPublicRepository(r.Context(), &JiaozifsResponse{w}, r, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // DeleteRepository operation middleware func (siw *ServerInterfaceWrapper) DeleteRepository(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -9422,6 +9798,75 @@ func (siw *ServerInterfaceWrapper) Merge(w http.ResponseWriter, r *http.Request) handler.ServeHTTP(w, r.WithContext(ctx)) } +// ChangeVisible operation middleware +func (siw *ServerInterfaceWrapper) ChangeVisible(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params ChangeVisibleParams + + // ------------- Required query parameter "visible" ------------- + + if paramValue := r.URL.Query().Get("visible"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "visible"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "visible", r.URL.Query(), ¶ms.Visible) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "visible", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ChangeVisible(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // GetSetupState operation middleware func (siw *ServerInterfaceWrapper) GetSetupState(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -10542,6 +10987,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/object/{owner}/{repository}", wrapper.UploadObject) }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/repos/public", wrapper.ListPublicRepository) + }) r.Group(func(r chi.Router) { r.Delete(options.BaseURL+"/repos/{owner}/{repository}", wrapper.DeleteRepository) }) @@ -10602,6 +11050,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/repos/{owner}/{repository}/mergerequest/{mrSeq}/merge", wrapper.Merge) }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/repos/{owner}/{repository}/visible", wrapper.ChangeVisible) + }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/setup", wrapper.GetSetupState) }) @@ -10666,96 +11117,98 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 9be8380e..f95232b7 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -475,6 +475,8 @@ components: type: string name: type: string + visible: + type: boolean blockstore_config: description: block storage config url encoded json type: string @@ -504,6 +506,7 @@ components: - id - name - owner_id + - visible - head - use_public_storage - creator_id @@ -518,6 +521,8 @@ components: owner_id: type: string format: uuid + visible: + type: boolean head: type: string use_public_storage: @@ -1952,6 +1957,40 @@ paths: 500: description: Internal Server Error + /repos/{owner}/{repository}/visible: + parameters: + - in: path + name: owner + required: true + schema: + type: string + - in: path + name: repository + required: true + schema: + type: string + post: + tags: + - repo + operationId: changeVisible + summary: change repository visible(true for public, false for private) + parameters: + - in: query + name: visible + required: true + schema: + type: boolean + responses: + 200: + description: Change repository visible success + 401: + description: Unauthorized + 404: + description: Resource Not Found + 420: + description: Too many requests + 500: + description: Internal Server Error /repos/{owner}/{repository}/members: parameters: - in: path @@ -2051,6 +2090,7 @@ paths: description: Too many requests 500: description: Internal Server Error + /repos/{owner}/{repository}/member/invite: parameters: - in: path @@ -2093,18 +2133,12 @@ paths: 500: description: Internal Server Error - /users/{owner}/repos: - parameters: - - in: path - name: owner - required: true - schema: - type: string + /repos/public: get: tags: - repo - operationId: listRepository - summary: list repository in specific owner + operationId: listPublicRepository + summary: list public repository in all system parameters: - $ref: "#/components/parameters/PaginationPrefix" - $ref: "#/components/parameters/PaginationInt64After" @@ -2115,35 +2149,45 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/RepositoryList" + $ref: "#/components/schemas/RepositoryList" 400: description: ValidationError 401: description: Unauthorized 403: description: Forbidden - - /groups/repo: + /users/{owner}/repos: + parameters: + - in: path + name: owner + required: true + schema: + type: string get: tags: - - group - operationId: listRepoGroup - summary: list groups for repo + - repo + operationId: listRepository + summary: list repository in specific owner + parameters: + - $ref: "#/components/parameters/PaginationPrefix" + - $ref: "#/components/parameters/PaginationInt64After" + - $ref: "#/components/parameters/PaginationAmount" responses: 200: - description: list repo's group + description: repository list content: application/json: schema: - type: array - items: - $ref: "#/components/schemas/Group" + $ref: "#/components/schemas/RepositoryList" 400: description: ValidationError 401: description: Unauthorized 403: description: Forbidden + + + /users/repos: # 必须授权 get: tags: @@ -2229,7 +2273,6 @@ paths: default: description: Internal Server Error - /repos/{owner}/{repository}/branch: parameters: - in: path @@ -2307,7 +2350,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/BranchCreation" + $ref: "#/components/schemas/Branch" 400: description: ValidationError 404: @@ -2319,7 +2362,27 @@ paths: default: description: Internal Server Error - + /groups/repo: + get: + tags: + - group + operationId: listRepoGroup + summary: list groups for repo + responses: + 200: + description: list repo's group + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Group" + 400: + description: ValidationError + 401: + description: Unauthorized + 403: + description: Forbidden /auth/login: post: diff --git a/auth/rbac/rbac.go b/auth/rbac/rbac.go index cf173a1f..e2eab151 100644 --- a/auth/rbac/rbac.go +++ b/auth/rbac/rbac.go @@ -4,11 +4,13 @@ import ( "context" "errors" "fmt" + "sync" "time" + "github.com/jiaozifs/jiaozifs/auth/rbac/wildcard" + "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/auth/rbac/wildcard" "github.com/jiaozifs/jiaozifs/models" "github.com/jiaozifs/jiaozifs/models/rbacmodel" ) @@ -29,6 +31,8 @@ const ( RepoWrite BuiltinGroupName = "RepoWrite" // RepoRead only read in this repo RepoRead BuiltinGroupName = "RepoRead" + // RepoViewer grant for viewer for public repository + RepoViewer BuiltinGroupName = "Viewer" // UserOwnAccess could manage user, credential, create repo UserOwnAccess BuiltinGroupName = "UserOwnAccess" ) @@ -71,6 +75,9 @@ var _ PermissionCheck = (*RbacAuth)(nil) type RbacAuth struct { //nolint db models.IRepo + + cacheLk sync.Mutex + viewerPolicies []*rbacmodel.Policy } func NewRbacAuth(IRepo models.IRepo) *RbacAuth { @@ -102,6 +109,13 @@ func (s *RbacAuth) InitRbac(ctx context.Context, adminUser *models.User) error { Resource: rbacmodel.RepoURArn(rbacmodel.UserIDCapture, rbacmodel.RepoIDCapture), Effect: rbacmodel.StatementEffectAllow, }, + { + Action: []string{ + rbacmodel.UpdateVisibleAction, //change visible only work for owner + }, + Resource: rbacmodel.RepoUArn(rbacmodel.UserIDCapture), + Effect: rbacmodel.StatementEffectDeny, + }, }, CreatedAt: time.Now(), UpdatedAt: time.Now(), @@ -131,6 +145,19 @@ func (s *RbacAuth) InitRbac(ctx context.Context, adminUser *models.User) error { return err } + // add repo viewer + _, err = s.addGroupPolicy(ctx, repo, RepoViewer, &rbacmodel.Policy{ + Name: RepoViewer, + //todo make viewer the same with repo read, but should decrease viewer permission in future + Statements: MakeStatementForPolicyTypeOrDie("RepoRead", []rbacmodel.Resource{rbacmodel.RepoURArn(rbacmodel.UserIDCapture, rbacmodel.RepoIDCapture)}), + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + //add user owner userOwner := MakeStatementForPolicyTypeOrDie("UserFullAccess", []rbacmodel.Resource{ rbacmodel.UserArn(rbacmodel.UserIDCapture), rbacmodel.UserAkskArn(rbacmodel.UserIDCapture), @@ -255,11 +282,31 @@ func (s *RbacAuth) getMemberPolicy(ctx context.Context, operatorID uuid.UUID, re return nil, err } - policy, err := s.db.PolicyRepo().List(ctx, rbacmodel.NewListPolicyParams().SetIDs(group.Policies...)) + policies, err := s.db.PolicyRepo().List(ctx, rbacmodel.NewListPolicyParams().SetIDs(group.Policies...)) + if err != nil { + return nil, err + } + return policies, err +} + +func (s *RbacAuth) getViewerPolicy(ctx context.Context) ([]*rbacmodel.Policy, error) { + if s.viewerPolicies != nil { + return s.viewerPolicies, nil + } + s.cacheLk.Lock() + defer s.cacheLk.Unlock() + + group, err := s.db.GroupRepo().Get(ctx, rbacmodel.NewGetGroupParams().SetName(RepoViewer)) if err != nil { return nil, err } - return policy, err + + policies, err := s.db.PolicyRepo().List(ctx, rbacmodel.NewListPolicyParams().SetIDs(group.Policies...)) + if err != nil { + return nil, err + } + s.viewerPolicies = policies + return policies, err } func (s *RbacAuth) AuthorizeMember(ctx context.Context, repoID uuid.UUID, req *AuthorizationRequest) (*AuthorizationResponse, error) { @@ -273,16 +320,26 @@ func (s *RbacAuth) AuthorizeMember(ctx context.Context, repoID uuid.UUID, req *A return &AuthorizationResponse{Allowed: true}, nil } - policies, err := s.getMemberPolicy(ctx, req.OperatorID, repoID) - if err != nil { - if errors.Is(err, models.ErrNotFound) { - return &AuthorizationResponse{ - Allowed: false, - Error: ErrInsufficientPermissions, - }, nil + var policies []*rbacmodel.Policy + if repo.Visible { + policies, err = s.getViewerPolicy(ctx) + if err != nil { + return nil, err } + } + + memberPolicies, err := s.getMemberPolicy(ctx, req.OperatorID, repoID) + if err != nil && !errors.Is(err, models.ErrNotFound) { return nil, err } + policies = append(policies, memberPolicies...) + + if len(policies) == 0 { + return &AuthorizationResponse{ + Allowed: false, + Error: ErrInsufficientPermissions, + }, nil + } resourceParams := ResourceParams{UserID: repo.OwnerID, RepoID: repoID} allowed := checkPermissions(ctx, req.RequiredPermissions, resourceParams, policies) diff --git a/auth/rbac/rbac_test.go b/auth/rbac/rbac_test.go index 259876d9..3a085874 100644 --- a/auth/rbac/rbac_test.go +++ b/auth/rbac/rbac_test.go @@ -66,10 +66,11 @@ func TestNewRbac(t *testing.T) { return commonUser } - addRepo := func(name string, ownerID uuid.UUID) *models.Repository { + addRepo := func(name string, ownerID uuid.UUID, isPublic bool) *models.Repository { repo, err := dbRepo.RepositoryRepo().Insert(ctx, &models.Repository{ Name: name, OwnerID: ownerID, + Visible: isPublic, HEAD: "master", }) require.NoError(t, err) @@ -91,7 +92,7 @@ func TestNewRbac(t *testing.T) { require.True(t, resp.Allowed) }) - t.Run("super user controller other user", func(t *testing.T) { + t.Run("super user control other user", func(t *testing.T) { commonUser := &models.User{ Name: "common", Email: "test@test.com", @@ -118,7 +119,7 @@ func TestNewRbac(t *testing.T) { require.True(t, resp.Allowed) }) - t.Run("common user controller himself", func(t *testing.T) { + t.Run("common user control himself", func(t *testing.T) { commonUser := addCommonUser("common1") resp, err := rbacChecker.Authorize(ctx, &rbac.AuthorizationRequest{ OperatorID: commonUser.ID, @@ -134,7 +135,7 @@ func TestNewRbac(t *testing.T) { require.True(t, resp.Allowed) }) - t.Run("common user cannot controller himself", func(t *testing.T) { + t.Run("common user cannot control other user", func(t *testing.T) { commonUser := addCommonUser("common2") resp, err := rbacChecker.Authorize(ctx, &rbac.AuthorizationRequest{ OperatorID: commonUser.ID, @@ -184,7 +185,7 @@ func TestNewRbac(t *testing.T) { require.True(t, resp.Allowed) }) - t.Run("super create others repo", func(t *testing.T) { + t.Run("super create branch in others's repo", func(t *testing.T) { commonUser := addCommonUser("common5") resp, err := rbacChecker.Authorize(ctx, &rbac.AuthorizationRequest{ OperatorID: superUser.ID, @@ -200,10 +201,10 @@ func TestNewRbac(t *testing.T) { require.True(t, resp.Allowed) }) - t.Run("cannt create branch in other user", func(t *testing.T) { + t.Run("common user cannot create branch in other user", func(t *testing.T) { commonUser := addCommonUser("common6") other1User := addCommonUser("other1") - repo := addRepo("aaa", other1User.ID) + repo := addRepo("aaa", other1User.ID, false) resp, err := rbacChecker.AuthorizeMember(ctx, repo.ID, &rbac.AuthorizationRequest{ OperatorID: commonUser.ID, RequiredPermissions: rbac.Node{ @@ -218,10 +219,10 @@ func TestNewRbac(t *testing.T) { require.False(t, resp.Allowed) }) - t.Run("can create branch in other user after add in member", func(t *testing.T) { + t.Run("can create branch in other user after add in member with write grant", func(t *testing.T) { commonUser := addCommonUser("common7") other1User := addCommonUser("other2") - repo := addRepo("aaa", other1User.ID) + repo := addRepo("aaa", other1User.ID, false) repoWriteGroup, err := dbRepo.GroupRepo().Get(ctx, rbacmodel.NewGetGroupParams().SetName(rbac.RepoWrite)) require.NoError(t, err) _, err = dbRepo.MemberRepo().Insert(ctx, &models.Member{ @@ -247,10 +248,10 @@ func TestNewRbac(t *testing.T) { require.True(t, resp.Allowed) }) - t.Run("can not create branch in other user after add in member", func(t *testing.T) { + t.Run("can not create branch in other user after add in member with read grant", func(t *testing.T) { commonUser := addCommonUser("common8") other1User := addCommonUser("other3") - repo := addRepo("aaa", other1User.ID) + repo := addRepo("aaa", other1User.ID, false) repoWriteGroup, err := dbRepo.GroupRepo().Get(ctx, rbacmodel.NewGetGroupParams().SetName(rbac.RepoRead)) require.NoError(t, err) _, err = dbRepo.MemberRepo().Insert(ctx, &models.Member{ @@ -276,9 +277,9 @@ func TestNewRbac(t *testing.T) { require.False(t, resp.Allowed) }) - t.Run("read own branch", func(t *testing.T) { + t.Run("owner read branch without grant", func(t *testing.T) { commonUser := addCommonUser("common9") - repo := addRepo("aaa", commonUser.ID) + repo := addRepo("aaa", commonUser.ID, false) resp, err := rbacChecker.AuthorizeMember(ctx, repo.ID, &rbac.AuthorizationRequest{ OperatorID: commonUser.ID, RequiredPermissions: rbac.Node{ @@ -292,4 +293,39 @@ func TestNewRbac(t *testing.T) { require.NoError(t, resp.Error) require.True(t, resp.Allowed) }) + + t.Run("test for public repo", func(t *testing.T) { + commonUser := addCommonUser("common10") + otherUser := addCommonUser("common11") + repo := addRepo("aaa", commonUser.ID, true) + t.Run("can read branch without grant", func(t *testing.T) { + resp, err := rbacChecker.AuthorizeMember(ctx, repo.ID, &rbac.AuthorizationRequest{ + OperatorID: otherUser.ID, + RequiredPermissions: rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ReadBranchAction, + Resource: rbacmodel.RepoURArn(commonUser.ID.String(), repo.ID.String()), + }, + }, + }) + require.NoError(t, err) + require.NoError(t, resp.Error) + require.True(t, resp.Allowed) + }) + + t.Run("cannot write branch without grant", func(t *testing.T) { + resp, err := rbacChecker.AuthorizeMember(ctx, repo.ID, &rbac.AuthorizationRequest{ + OperatorID: otherUser.ID, + RequiredPermissions: rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.CreateCommitAction, + Resource: rbacmodel.RepoURArn(commonUser.ID.String(), repo.ID.String()), + }, + }, + }) + require.NoError(t, err) + require.Equal(t, rbac.ErrInsufficientPermissions, resp.Error) + require.False(t, resp.Allowed) + }) + }) } diff --git a/controller/group.go b/controller/group_ctl.go similarity index 100% rename from controller/group.go rename to controller/group_ctl.go diff --git a/controller/member.go b/controller/member_ctl.go similarity index 100% rename from controller/member.go rename to controller/member_ctl.go diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index 05bde41f..536c8b46 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -144,6 +144,43 @@ func (repositoryCtl RepositoryController) ListRepository(ctx context.Context, w }) } +func (repositoryCtl RepositoryController) ListPublicRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, params api.ListPublicRepositoryParams) { + listRepoParams := models.NewListRepoParams().SetVisible(true) + if params.Prefix != nil && len(*params.Prefix) > 0 { + listRepoParams.SetName(*params.Prefix, models.PrefixMatch) + } + if params.After != nil { + listRepoParams.SetAfter(time.UnixMilli(*params.After)) + } + pageAmount := utils.IntValue(params.Amount) + if pageAmount > utils.DefaultMaxPerPage || pageAmount <= 0 { + listRepoParams.SetAmount(utils.DefaultMaxPerPage) + } else { + listRepoParams.SetAmount(pageAmount) + } + + repositories, hasMore, err := repositoryCtl.Repo.RepositoryRepo().List(ctx, listRepoParams) + if err != nil { + w.Error(err) + return + } + results := make([]api.Repository, 0, len(repositories)) + for _, repo := range repositories { + results = append(results, *repositoryToDto(repo)) + } + pagMag := utils.PaginationFor(hasMore, results, "UpdatedAt") + pagination := api.Pagination{ + HasMore: pagMag.HasMore, + MaxPerPage: pagMag.MaxPerPage, + NextOffset: pagMag.NextOffset, + Results: pagMag.Results, + } + w.JSON(api.RepositoryList{ + Pagination: pagination, + Results: results, + }) +} + func (repositoryCtl RepositoryController) CreateRepository(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.CreateRepositoryJSONRequestBody) { err := validator.ValidateRepoName(body.Name) if err != nil { @@ -200,6 +237,7 @@ func (repositoryCtl RepositoryController) CreateRepository(ctx context.Context, repository := &models.Repository{ ID: repoID, Name: body.Name, + Visible: utils.BoolValue(body.Visible), UsePublicStorage: usePublicStorage, StorageAdapterParams: &storageConfig, StorageNamespace: storageNamespace, @@ -476,6 +514,37 @@ func (repositoryCtl RepositoryController) GetCommitsInRef(ctx context.Context, w w.JSON(commits) } +func (repositoryCtl RepositoryController) ChangeVisible(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.ChangeVisibleParams) { + owner, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + repo, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) + if err != nil { + w.Error(err) + return + } + + if !repositoryCtl.authorizeMember(ctx, w, repo.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.UpdateVisibleAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repo.ID.String()), + }, + }) { + return + } + + updateParams := models.NewUpdateRepoParams(repo.ID).SetVisible(params.Visible) + err = repositoryCtl.Repo.RepositoryRepo().UpdateByID(ctx, updateParams) + if err != nil { + w.Error(err) + return + } + w.OK() +} + func repositoryToDto(repository *models.Repository) *api.Repository { return &api.Repository{ CreatedAt: repository.CreatedAt.UnixMilli(), diff --git a/integrationtest/aksk_test.go b/integrationtest/aksk_test.go index e69dcebf..1e9de7f8 100644 --- a/integrationtest/aksk_test.go +++ b/integrationtest/aksk_test.go @@ -92,8 +92,7 @@ func AkSkSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("delete ak by id", func() { - aksk, err := createAksk(ctx, client) - convey.So(err, convey.ShouldBeNil) + aksk := createAksk(ctx, client) resp, err := client.DeleteAksk(ctx, &api.DeleteAkskParams{Id: &aksk.Id}) convey.So(err, convey.ShouldBeNil) @@ -101,8 +100,7 @@ func AkSkSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("delete ak by ak", func() { - aksk, err := createAksk(ctx, client) - convey.So(err, convey.ShouldBeNil) + aksk := createAksk(ctx, client) resp, err := client.DeleteAksk(ctx, &api.DeleteAkskParams{AccessKey: &aksk.AccessKey}) convey.So(err, convey.ShouldBeNil) @@ -117,11 +115,11 @@ func AkSkSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("list aksk", func(c convey.C) { c.Convey("prepare aksk", func() { - _, _ = createAksk(ctx, client) - _, _ = createAksk(ctx, client) - _, _ = createAksk(ctx, client) - _, _ = createAksk(ctx, client) - _, _ = createAksk(ctx, client) + _ = createAksk(ctx, client) + _ = createAksk(ctx, client) + _ = createAksk(ctx, client) + _ = createAksk(ctx, client) + _ = createAksk(ctx, client) }) c.Convey("no auth", func() { re := client.RequestEditors @@ -145,8 +143,7 @@ func AkSkSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("aksk user", func(c convey.C) { c.Convey("success", func(c convey.C) { - aksk, err := createAksk(ctx, client) - convey.So(err, convey.ShouldBeNil) + aksk := createAksk(ctx, client) cli, err := api.NewClient(urlStr+apiimpl.APIV1Prefix, api.AkSkOption(aksk.AccessKey, aksk.SecretKey)) convey.So(err, convey.ShouldBeNil) @@ -159,8 +156,7 @@ func AkSkSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.ShouldEqual(user.JSON200.Name, userName) }) c.Convey("wrong sk", func(c convey.C) { - aksk, err := createAksk(ctx, client) - convey.So(err, convey.ShouldBeNil) + aksk := createAksk(ctx, client) client, err := api.NewClient(urlStr+apiimpl.APIV1Prefix, api.AkSkOption(aksk.AccessKey, "fakesk")) convey.So(err, convey.ShouldBeNil) diff --git a/integrationtest/branch_test.go b/integrationtest/branch_test.go index d9f6b0da..3d985800 100644 --- a/integrationtest/branch_test.go +++ b/integrationtest/branch_test.go @@ -24,10 +24,9 @@ func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { prefix := "feat/" c.Convey("init", func(c convey.C) { - createUser(ctx, client, userName) + _ = createUser(ctx, client, userName) loginAndSwitch(ctx, client, userName, false) - createRepo(ctx, client, repoName) - + _ = createRepo(ctx, client, repoName, false) }) c.Convey("create branch", func(c convey.C) { c.Convey("no auth", func() { diff --git a/integrationtest/commit_test.go b/integrationtest/commit_test.go index 2d9a6bb8..e3e8a706 100644 --- a/integrationtest/commit_test.go +++ b/integrationtest/commit_test.go @@ -21,14 +21,14 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { branchName := "feat/get_entries_test" c.Convey("init", func(c convey.C) { - createUser(ctx, client, userName) + _ = createUser(ctx, client, userName) loginAndSwitch(ctx, client, userName, false) - createRepo(ctx, client, repoName) - createBranch(ctx, client, userName, repoName, "main", branchName) - createWip(ctx, client, userName, repoName, branchName) - uploadObject(ctx, client, userName, repoName, branchName, "m.dat") - uploadObject(ctx, client, userName, repoName, branchName, "g/x.dat") - uploadObject(ctx, client, userName, repoName, branchName, "g/m.dat") + _ = createRepo(ctx, client, repoName, false) + _ = createBranch(ctx, client, userName, repoName, "main", branchName) + _ = createWip(ctx, client, userName, repoName, branchName) + _ = uploadObject(ctx, client, userName, repoName, branchName, "m.dat") + _ = uploadObject(ctx, client, userName, repoName, branchName, "g/x.dat") + _ = uploadObject(ctx, client, userName, repoName, branchName, "g/m.dat") }) c.Convey("get wip entries", func(c convey.C) { @@ -387,7 +387,7 @@ func GetCommitChangesSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("init", func(c convey.C) { createUser(ctx, client, userName) loginAndSwitch(ctx, client, userName, false) - createRepo(ctx, client, repoName) + createRepo(ctx, client, repoName, false) createWip(ctx, client, userName, repoName, "main") uploadObject(ctx, client, userName, repoName, "main", "m.dat") commitWip(ctx, client, userName, repoName, "main", "test") diff --git a/integrationtest/group_test.go b/integrationtest/group_test.go index cbc82e73..ec0c9168 100644 --- a/integrationtest/group_test.go +++ b/integrationtest/group_test.go @@ -12,7 +12,7 @@ func GroupSpec(ctx context.Context, urlStr string) func(c convey.C) { client, _ := api.NewClient(urlStr + apiimpl.APIV1Prefix) return func(c convey.C) { userName := "grouptest" - createUser(ctx, client, userName) + _ = createUser(ctx, client, userName) loginAndSwitch(ctx, client, userName, false) resp, err := client.ListRepoGroup(ctx) diff --git a/integrationtest/helper_test.go b/integrationtest/helper_test.go index 4c527c92..b91e9686 100644 --- a/integrationtest/helper_test.go +++ b/integrationtest/helper_test.go @@ -108,7 +108,7 @@ func SetupDaemon(t *testing.T, ctx context.Context) (string, Closer) { //nolint var count atomic.Int32 -func createUser(ctx context.Context, client *api.Client, userName string) { +func createUser(ctx context.Context, client *api.Client, userName string) *api.UserInfo { resp, err := client.Register(ctx, api.RegisterJSONRequestBody{ Name: userName, Password: "12345678", @@ -116,6 +116,10 @@ func createUser(ctx context.Context, client *api.Client, userName string) { }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + + result, err := api.ParseRegisterResponse(resp) + convey.So(err, convey.ShouldBeNil) + return result.JSON201 } func loginAndSwitch(ctx context.Context, client *api.Client, userName string, useCookie bool) { @@ -141,30 +145,43 @@ func loginAndSwitch(ctx context.Context, client *api.Client, userName string, us }) } -func createBranch(ctx context.Context, client *api.Client, user string, repoName string, source, refName string) { +func createBranch(ctx context.Context, client *api.Client, user string, repoName string, source, refName string) *api.Branch { resp, err := client.CreateBranch(ctx, user, repoName, api.CreateBranchJSONRequestBody{ Source: source, Name: refName, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + + result, err := api.ParseCreateBranchResponse(resp) + convey.So(err, convey.ShouldBeNil) + return result.JSON201 } -func createRepo(ctx context.Context, client *api.Client, repoName string) { +func createRepo(ctx context.Context, client *api.Client, repoName string, visible bool) *api.Repository { resp, err := client.CreateRepository(ctx, api.CreateRepositoryJSONRequestBody{ - Name: repoName, + Name: repoName, + Visible: utils.Bool(visible), }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + + result, err := api.ParseCreateRepositoryResponse(resp) + convey.So(err, convey.ShouldBeNil) + return result.JSON201 } -func uploadObject(ctx context.Context, client *api.Client, user string, repoName string, refName string, path string) { //nolint +func uploadObject(ctx context.Context, client *api.Client, user string, repoName string, refName string, path string) *api.ObjectStats { //nolint resp, err := client.UploadObjectWithBody(ctx, user, repoName, &api.UploadObjectParams{ RefName: refName, Path: path, }, "application/octet-stream", io.LimitReader(rand.Reader, 50)) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + + result, err := api.ParseUploadObjectResponse(resp) + convey.So(err, convey.ShouldBeNil) + return result.JSON201 } func deleteObject(ctx context.Context, client *api.Client, user string, repoName string, refName string, path string) { //nolint @@ -176,12 +193,16 @@ func deleteObject(ctx context.Context, client *api.Client, user string, repoName convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) } -func createWip(ctx context.Context, client *api.Client, user string, repoName string, refName string) { +func createWip(ctx context.Context, client *api.Client, user string, repoName string, refName string) *api.Wip { resp, err := client.GetWip(ctx, user, repoName, &api.GetWipParams{ RefName: refName, }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + + result, err := api.ParseGetWipResponse(resp) + convey.So(err, convey.ShouldBeNil) + return result.JSON200 } func commitWip(ctx context.Context, client *api.Client, user string, repoName string, refName string, msg string) { @@ -194,7 +215,7 @@ func commitWip(ctx context.Context, client *api.Client, user string, repoName st convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) } -func createMergeRequest(ctx context.Context, client *api.Client, user string, repoName string, sourceBranch string, targetBranch string) { +func createMergeRequest(ctx context.Context, client *api.Client, user string, repoName string, sourceBranch string, targetBranch string) *api.MergeRequest { resp, err := client.CreateMergeRequest(ctx, user, repoName, api.CreateMergeRequestJSONRequestBody{ Description: utils.String("create merge request test"), SourceBranchName: sourceBranch, @@ -203,17 +224,17 @@ func createMergeRequest(ctx context.Context, client *api.Client, user string, re }) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + + result, err := api.ParseCreateMergeRequestResponse(resp) + convey.So(err, convey.ShouldBeNil) + return result.JSON201 } -func createAksk(ctx context.Context, client *api.Client) (*api.Aksk, error) { +func createAksk(ctx context.Context, client *api.Client) *api.Aksk { resp, err := client.CreateAksk(ctx, &api.CreateAkskParams{Description: utils.String("create ak sk")}) - if err != nil { - return nil, err - } + convey.So(err, convey.ShouldBeNil) akskResult, err := api.ParseCreateAkskResponse(resp) - if err != nil { - return nil, err - } - return akskResult.JSON201, nil + convey.So(err, convey.ShouldBeNil) + return akskResult.JSON201 } diff --git a/integrationtest/member_test.go b/integrationtest/member_test.go index 6d4f2236..16211f22 100644 --- a/integrationtest/member_test.go +++ b/integrationtest/member_test.go @@ -28,28 +28,18 @@ func MemberSpec(ctx context.Context, urlStr string) func(c convey.C) { return func(c convey.C) { c.Convey("init test", func(c convey.C) { var err error - createUser(ctx, client, user1Name) - user1Token, err = getToken(ctx, client, user1Name) - convey.ShouldBeNil(c, err) + user1 = createUser(ctx, client, user1Name) + user1Token = getToken(ctx, client, user1Name) client.RequestEditors = user1Token - createRepo(ctx, client, testRepoName) - user1, err = getUser(ctx, client) - convey.ShouldBeNil(c, err) - repo1, err = getRepo(ctx, client, user1Name, testRepoName) - convey.ShouldBeNil(c, err) + repo1 = createRepo(ctx, client, testRepoName, false) client.RequestEditors = nil - createUser(ctx, client, user2Name) - user2Token, err = getToken(ctx, client, user2Name) - convey.ShouldBeNil(c, err) + user2 = createUser(ctx, client, user2Name) + user2Token = getToken(ctx, client, user2Name) client.RequestEditors = user2Token - createRepo(ctx, client, testRepo2Name) - user2, err = getUser(ctx, client) - convey.ShouldBeNil(c, err) - repo2, err = getRepo(ctx, client, user2Name, testRepo2Name) - convey.ShouldBeNil(c, err) + repo2 = createRepo(ctx, client, testRepo2Name, false) readGroup, writeGroup, adminGroup, err = getGroup(ctx, client) convey.ShouldNotBeNil(adminGroup) @@ -315,18 +305,6 @@ func MemberSpec(ctx context.Context, urlStr string) func(c convey.C) { } } -func getUser(ctx context.Context, client *api.Client) (*api.UserInfo, error) { - resp, err := client.GetUserInfo(ctx) - if err != nil { - return nil, err - } - result, err := api.ParseGetUserInfoResponse(resp) - if err != nil { - return nil, err - } - return result.JSON200, nil -} - func getGroup(ctx context.Context, client *api.Client) (*api.Group, *api.Group, *api.Group, error) { resp, err := client.ListRepoGroup(ctx) if err != nil { @@ -354,32 +332,24 @@ func getGroup(ctx context.Context, client *api.Client) (*api.Group, *api.Group, return &readGroup, &writeGroup, &adminGroup, nil } -func getRepo(ctx context.Context, client *api.Client, owner, repoName string) (*api.Repository, error) { +func getRepo(ctx context.Context, client *api.Client, owner, repoName string) *api.Repository { resp, err := client.GetRepository(ctx, owner, repoName) - if err != nil { - return nil, err - } + convey.So(err, convey.ShouldBeNil) result, err := api.ParseGetRepositoryResponse(resp) - if err != nil { - return nil, err - } - return result.JSON200, nil + convey.So(err, convey.ShouldBeNil) + return result.JSON200 } -func getToken(ctx context.Context, client *api.Client, userName string) ([]api.RequestEditorFn, error) { +func getToken(ctx context.Context, client *api.Client, userName string) []api.RequestEditorFn { resp, err := client.Login(ctx, api.LoginJSONRequestBody{ Name: userName, Password: "12345678", }) - if err != nil { - return nil, err - } + convey.So(err, convey.ShouldBeNil) loginResult, err := api.ParseLoginResponse(resp) - if err != nil { - return nil, err - } + convey.So(err, convey.ShouldBeNil) return []api.RequestEditorFn{func(ctx context.Context, req *http.Request) error { req.Header.Add("Authorization", "Bearer "+loginResult.JSON200.Token) return nil - }}, nil + }} } diff --git a/integrationtest/merge_request_test.go b/integrationtest/merge_request_test.go index 4811f690..195190ba 100644 --- a/integrationtest/merge_request_test.go +++ b/integrationtest/merge_request_test.go @@ -25,12 +25,12 @@ func MergeRequestSpec(ctx context.Context, urlStr string) func(c convey.C) { branchName := "feat/obj_test" c.Convey("init", func(c convey.C) { - createUser(ctx, client, userName) + _ = createUser(ctx, client, userName) loginAndSwitch(ctx, client, userName, false) - createRepo(ctx, client, repoName) - createBranch(ctx, client, userName, repoName, "main", branchName) - createWip(ctx, client, userName, repoName, branchName) - uploadObject(ctx, client, userName, repoName, branchName, "a.bin") + _ = createRepo(ctx, client, repoName, false) + _ = createBranch(ctx, client, userName, repoName, "main", branchName) + _ = createWip(ctx, client, userName, repoName, branchName) + _ = uploadObject(ctx, client, userName, repoName, branchName, "a.bin") commitWip(ctx, client, userName, repoName, branchName, "test") }) @@ -266,7 +266,6 @@ func MergeRequestSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("list merge request", func(c convey.C) { - c.Convey("no auth", func() { re := client.RequestEditors client.RequestEditors = nil @@ -355,7 +354,6 @@ func MergeRequestSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("merge request", func(c convey.C) { - c.Convey("no auth", func() { re := client.RequestEditors client.RequestEditors = nil diff --git a/integrationtest/objects_test.go b/integrationtest/objects_test.go index 8321b760..49a05341 100644 --- a/integrationtest/objects_test.go +++ b/integrationtest/objects_test.go @@ -22,11 +22,11 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { branchName := "feat/obj_test" c.Convey("init", func(c convey.C) { - createUser(ctx, client, userName) + _ = createUser(ctx, client, userName) loginAndSwitch(ctx, client, userName, false) - createRepo(ctx, client, repoName) - createBranch(ctx, client, userName, repoName, "main", branchName) - createWip(ctx, client, userName, repoName, branchName) + _ = createRepo(ctx, client, repoName, false) + _ = createBranch(ctx, client, userName, repoName, "main", branchName) + _ = createWip(ctx, client, userName, repoName, branchName) }) c.Convey("upload object", func(c convey.C) { c.Convey("no auth", func() { diff --git a/integrationtest/public_repo_test.go b/integrationtest/public_repo_test.go new file mode 100644 index 00000000..90cf78d5 --- /dev/null +++ b/integrationtest/public_repo_test.go @@ -0,0 +1,156 @@ +package integrationtest + +import ( + "context" + "fmt" + "net/http" + "strconv" + + "github.com/jiaozifs/jiaozifs/utils" + + "github.com/jiaozifs/jiaozifs/api" + apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" + "github.com/smartystreets/goconvey/convey" +) + +func PublicRepoSpec(ctx context.Context, urlStr string) func(c convey.C) { + client, _ := api.NewClient(urlStr + apiimpl.APIV1Prefix) + user1Name := "pubuser" + testRepoName := "test_repo" + + user2Name := "otheruser" + testRepo2Name := "test_repo2" + + var user1Token, user2Token []api.RequestEditorFn + return func(c convey.C) { + + c.Convey("init", func(c convey.C) { + _ = createUser(ctx, client, user1Name) + _ = createUser(ctx, client, user2Name) + user1Token = getToken(ctx, client, user1Name) + user2Token = getToken(ctx, client, user2Name) + + client.RequestEditors = user1Token + _ = createRepo(ctx, client, testRepoName, false) + + client.RequestEditors = user2Token + _ = createRepo(ctx, client, testRepo2Name, false) + + client.RequestEditors = user1Token + }) + + c.Convey("change visiable", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.ChangeVisible(ctx, user1Name, testRepoName, &api.ChangeVisibleParams{Visible: true}) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("non exit user", func() { + resp, err := client.ChangeVisible(ctx, "mockUser", testRepoName, &api.ChangeVisibleParams{Visible: true}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("non exit repo", func() { + resp, err := client.ChangeVisible(ctx, user1Name, "mockRepo", &api.ChangeVisibleParams{Visible: true}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("change repo visible in others repo", func() { + resp, err := client.ChangeVisible(ctx, user2Name, testRepo2Name, &api.ChangeVisibleParams{Visible: true}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("success change visible", func() { + repoBeforeUpdated := getRepo(ctx, client, user1Name, testRepoName) + convey.ShouldBeFalse(repoBeforeUpdated.Head) + + resp, err := client.ChangeVisible(ctx, user1Name, testRepoName, &api.ChangeVisibleParams{Visible: true}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + repoAfterUpdated := getRepo(ctx, client, user1Name, testRepoName) + convey.ShouldBeTrue(repoAfterUpdated.Head) + }) + }) + + c.Convey("check permission", func(c convey.C) { + c.Convey("init", func() { + resp, err := client.ChangeVisible(ctx, user1Name, testRepoName, &api.ChangeVisibleParams{Visible: false}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + client.RequestEditors = user2Token + }) + + c.Convey("cannot read branch", func() { + resp, err := client.GetBranch(ctx, user1Name, testRepoName, &api.GetBranchParams{RefName: "main"}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("cannot create branch", func() { + client.RequestEditors = user1Token + resp, err := client.ChangeVisible(ctx, user1Name, testRepoName, &api.ChangeVisibleParams{Visible: true}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + client.RequestEditors = user2Token + }) + + c.Convey("can read branch", func() { + resp, err := client.GetBranch(ctx, user1Name, testRepoName, &api.GetBranchParams{RefName: "main"}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + }) + }) + + c.Convey("list public repo", func() { + resp, err := client.ListPublicRepository(ctx, &api.ListPublicRepositoryParams{}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseListPublicRepositoryResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.ShouldHaveLength(1, result.JSON200.Results) + }) + + c.Convey("list many repo", func() { + for i := 0; i < 20; i++ { + _ = createRepo(ctx, client, fmt.Sprintf("aa%d", i), true) + } + repo := createRepo(ctx, client, "aa21", true) + _ = createRepo(ctx, client, "aa22", true) + + after := repo.UpdatedAt + count := 0 + for { + resp, err := client.ListPublicRepository(ctx, &api.ListPublicRepositoryParams{ + Prefix: utils.String("aa"), + After: utils.Int64(after), + Amount: utils.Int(2), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseListPublicRepositoryResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.ShouldHaveLength(1, result.JSON200.Results) + + count = count + len(result.JSON200.Results) + if !result.JSON200.Pagination.HasMore { + break + } + convey.ShouldHaveLength(2, result.JSON200.Results) + after, err = strconv.ParseInt(result.JSON200.Pagination.NextOffset, 10, 64) + convey.So(err, convey.ShouldBeNil) + } + convey.ShouldEqual(21, count) + }) + } +} diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index bf7a9b46..283a6b58 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -19,9 +19,9 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("init", func(c convey.C) { loginAndSwitch(ctx, client, "admin2", true) - createRepo(ctx, client, "admin2_repo") + _ = createRepo(ctx, client, "admin2_repo", false) - createUser(ctx, client, userName) + _ = createUser(ctx, client, userName) loginAndSwitch(ctx, client, userName, false) }) diff --git a/integrationtest/root_test.go b/integrationtest/root_test.go index 8e870cb5..c528f792 100644 --- a/integrationtest/root_test.go +++ b/integrationtest/root_test.go @@ -27,4 +27,5 @@ func TestSpec(t *testing.T) { convey.Convey("merge request test", t, MergeRequestSpec(ctx, urlStr)) convey.Convey("group test", t, GroupSpec(ctx, urlStr)) convey.Convey("member test", t, MemberSpec(ctx, urlStr)) + convey.Convey("public repo test", t, PublicRepoSpec(ctx, urlStr)) } diff --git a/integrationtest/user_test.go b/integrationtest/user_test.go index f58cdcac..1fd03bf1 100644 --- a/integrationtest/user_test.go +++ b/integrationtest/user_test.go @@ -17,7 +17,7 @@ func UserSpec(ctx context.Context, urlStr string) func(c convey.C) { userName := "admin2" c.Convey("init user", func(c convey.C) { - createUser(ctx, client, userName) + _ = createUser(ctx, client, userName) }) c.Convey("invalid username", func() { diff --git a/integrationtest/wip_object_test.go b/integrationtest/wip_object_test.go index dc97e6cd..472b9761 100644 --- a/integrationtest/wip_object_test.go +++ b/integrationtest/wip_object_test.go @@ -20,13 +20,13 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { branchName := "feat/wip_obj_test" c.Convey("init", func(c convey.C) { - createUser(ctx, client, userName) + _ = createUser(ctx, client, userName) loginAndSwitch(ctx, client, userName, false) - createRepo(ctx, client, repoName) - createBranch(ctx, client, userName, repoName, "main", branchName) - createWip(ctx, client, userName, repoName, branchName) - uploadObject(ctx, client, userName, repoName, branchName, "m.dat") - uploadObject(ctx, client, userName, repoName, branchName, "g/m.dat") + _ = createRepo(ctx, client, repoName, false) + _ = createBranch(ctx, client, userName, repoName, "main", branchName) + _ = createWip(ctx, client, userName, repoName, branchName) + _ = uploadObject(ctx, client, userName, repoName, branchName, "m.dat") + _ = uploadObject(ctx, client, userName, repoName, branchName, "g/m.dat") }) c.Convey("head object", func(c convey.C) { @@ -475,17 +475,17 @@ func UpdateWipSpec(ctx context.Context, urlStr string) func(c convey.C) { branchName := "main" c.Convey("create wip", func(c convey.C) { - createUser(ctx, client, userName) + _ = createUser(ctx, client, userName) loginAndSwitch(ctx, client, userName, false) - createRepo(ctx, client, repoName) - createWip(ctx, client, userName, repoName, branchName) + _ = createRepo(ctx, client, repoName, false) + _ = createWip(ctx, client, userName, repoName, branchName) //make wip base commit has value - uploadObject(ctx, client, userName, repoName, branchName, "a.txt") + _ = uploadObject(ctx, client, userName, repoName, branchName, "a.txt") commitWip(ctx, client, userName, repoName, branchName, "test") - uploadObject(ctx, client, userName, repoName, branchName, "m.dat") - uploadObject(ctx, client, userName, repoName, branchName, "g/m.dat") + _ = uploadObject(ctx, client, userName, repoName, branchName, "m.dat") + _ = uploadObject(ctx, client, userName, repoName, branchName, "g/m.dat") }) c.Convey("get wip", func(c convey.C) { diff --git a/integrationtest/wip_test.go b/integrationtest/wip_test.go index 198738dd..264ce4a8 100644 --- a/integrationtest/wip_test.go +++ b/integrationtest/wip_test.go @@ -18,11 +18,11 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { branchNameForDelete := "feat/wip_test2" c.Convey("init", func(c convey.C) { - createUser(ctx, client, userName) + _ = createUser(ctx, client, userName) loginAndSwitch(ctx, client, userName, false) - createRepo(ctx, client, repoName) - createBranch(ctx, client, userName, repoName, "main", branchName) - createBranch(ctx, client, userName, repoName, "main", branchNameForDelete) + _ = createRepo(ctx, client, repoName, false) + _ = createBranch(ctx, client, userName, repoName, "main", branchName) + _ = createBranch(ctx, client, userName, repoName, "main", branchNameForDelete) }) c.Convey("list non exit wip", func(c convey.C) { @@ -36,7 +36,7 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("create wip", func() { - createWip(ctx, client, userName, repoName, "main") + _ = createWip(ctx, client, userName, repoName, "main") }) c.Convey("get wip", func(c convey.C) { @@ -180,7 +180,7 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("creat wip for test delete", func() { - createWip(ctx, client, userName, repoName, branchNameForDelete) + _ = createWip(ctx, client, userName, repoName, branchNameForDelete) }) c.Convey("delete branch successful", func() { diff --git a/models/rbacmodel/actions.gen.go b/models/rbacmodel/actions.gen.go index 1b79f025..fe331aca 100644 --- a/models/rbacmodel/actions.gen.go +++ b/models/rbacmodel/actions.gen.go @@ -8,6 +8,7 @@ var Actions = []string{ "repo:UpdateRepository", "repo:DeleteRepository", "repo:ListRepositories", + "repo:UpdateVisible", "repo:ReadObject", "repo:WriteObject", "repo:DeleteObject", diff --git a/models/rbacmodel/actions.go b/models/rbacmodel/actions.go index cd69dbfb..13dc926e 100644 --- a/models/rbacmodel/actions.go +++ b/models/rbacmodel/actions.go @@ -20,6 +20,8 @@ const ( DeleteRepositoryAction = "repo:DeleteRepository" ListRepositoriesAction = "repo:ListRepositories" + UpdateVisibleAction = "repo:UpdateVisible" + ReadObjectAction = "repo:ReadObject" WriteObjectAction = "repo:WriteObject" DeleteObjectAction = "repo:DeleteObject" diff --git a/models/rbacmodel/actions_test.go b/models/rbacmodel/actions_test.go index a557b383..d8b86119 100644 --- a/models/rbacmodel/actions_test.go +++ b/models/rbacmodel/actions_test.go @@ -3,6 +3,8 @@ package rbacmodel_test import ( "testing" + "github.com/stretchr/testify/require" + "github.com/jiaozifs/jiaozifs/models/rbacmodel" "golang.org/x/exp/slices" ) @@ -22,3 +24,9 @@ func TestAllActions(t *testing.T) { t.Errorf("Expected actions %v not to include IsValidAction", actions) } } + +func TestIsValidAction(t *testing.T) { + require.NoError(t, rbacmodel.IsValidAction("repo:test")) + require.Error(t, rbacmodel.IsValidAction("repo")) + require.Error(t, rbacmodel.IsValidAction("aaa:test")) +} diff --git a/models/repository.go b/models/repository.go index 542bc551..7f8cd435 100644 --- a/models/repository.go +++ b/models/repository.go @@ -15,13 +15,16 @@ type Repository struct { OwnerID uuid.UUID `bun:"owner_id,unique:name_owner_unique,type:uuid,notnull" json:"owner_id"` HEAD string `bun:"head,notnull" json:"head"` + // Visible indicate if this repo is public, false for private, true for public + Visible bool `bun:"visible,notnull" json:"visible"` UsePublicStorage bool `bun:"use_public_storage,notnull" json:"use_public_storage"` StorageNamespace *string `bun:"storage_namespace" json:"storage_namespace,omitempty"` StorageAdapterParams *string `bun:"storage_adapter_params" json:"storage_adapter_params,omitempty"` - Description *string `bun:"description" json:"description,omitempty"` - CreatorID uuid.UUID `bun:"creator_id,type:uuid,notnull" json:"creator_id"` + Description *string `bun:"description" json:"description,omitempty"` + + CreatorID uuid.UUID `bun:"creator_id,type:uuid,notnull" json:"creator_id"` CreatedAt time.Time `bun:"created_at,type:timestamp,notnull" json:"created_at"` UpdatedAt time.Time `bun:"updated_at,type:timestamp,notnull" json:"updated_at"` @@ -64,8 +67,10 @@ type ListRepoParams struct { ownerID uuid.UUID name *string nameMatch MatchMode - after *time.Time - amount int + visible *bool + + after *time.Time + amount int } func NewListRepoParams() *ListRepoParams { @@ -92,6 +97,11 @@ func (lrp *ListRepoParams) SetCreatorID(creatorID uuid.UUID) *ListRepoParams { return lrp } +func (lrp *ListRepoParams) SetVisible(visible bool) *ListRepoParams { + lrp.visible = &visible + return lrp +} + func (lrp *ListRepoParams) SetAfter(after time.Time) *ListRepoParams { lrp.after = &after return lrp @@ -130,6 +140,7 @@ func (drp *DeleteRepoParams) SetName(name string) *DeleteRepoParams { type UpdateRepoParams struct { id uuid.UUID description *string + visible *bool head *string } @@ -149,6 +160,11 @@ func (up *UpdateRepoParams) SetHead(head string) *UpdateRepoParams { return up } +func (up *UpdateRepoParams) SetVisible(visible bool) *UpdateRepoParams { + up.visible = &visible + return up +} + type IRepositoryRepo interface { Insert(ctx context.Context, repo *Repository) (*Repository, error) Get(ctx context.Context, params *GetRepoParams) (*Repository, error) @@ -215,6 +231,10 @@ func (r *RepositoryRepo) List(ctx context.Context, params *ListRepoParams) ([]*R query = query.Where("owner_id = ?", params.ownerID) } + if params.visible != nil { + query = query.Where("visible = ?", *params.visible) + } + if params.name != nil { switch params.nameMatch { case ExactMatch: @@ -228,7 +248,7 @@ func (r *RepositoryRepo) List(ctx context.Context, params *ListRepoParams) ([]*R } } - query = query.Order("updated_at DESC") + query = query.Order("updated_at DESC") // from new to old if params.after != nil { query = query.Where("updated_at < ?", *params.after) } @@ -264,12 +284,19 @@ func (r *RepositoryRepo) Delete(ctx context.Context, params *DeleteRepoParams) ( func (r *RepositoryRepo) UpdateByID(ctx context.Context, updateModel *UpdateRepoParams) error { updateQuery := r.db.NewUpdate().Model((*Repository)(nil)).Where("id = ?", updateModel.id) + if updateModel.description != nil { updateQuery.Set("description = ?", *updateModel.description) } + if updateModel.head != nil { updateQuery.Set("head = ?", *updateModel.head) } + + if updateModel.visible != nil { + updateQuery.Set("visible = ?", *updateModel.visible) + } + _, err := updateQuery.Exec(ctx) return err } diff --git a/models/repository_test.go b/models/repository_test.go index b4948cef..23ff78b3 100644 --- a/models/repository_test.go +++ b/models/repository_test.go @@ -33,6 +33,18 @@ func TestRepositoryUpdate(t *testing.T) { require.Equal(t, newRepo.HEAD, user.HEAD) }) + t.Run("only update visible", func(t *testing.T) { + repoModel := &models.Repository{} + require.NoError(t, gofakeit.Struct(repoModel)) + newRepo, err := repo.Insert(ctx, repoModel) + require.NoError(t, err) + err = repo.UpdateByID(ctx, models.NewUpdateRepoParams(newRepo.ID).SetVisible(!newRepo.Visible)) + require.NoError(t, err) + user, err := repo.Get(ctx, models.NewGetRepoParams().SetID(newRepo.ID)) + require.NoError(t, err) + require.Equal(t, !newRepo.Visible, user.Visible) + }) + t.Run("update all fields", func(t *testing.T) { repoModel := &models.Repository{} require.NoError(t, gofakeit.Struct(repoModel)) @@ -57,6 +69,7 @@ func TestRepositoryRepoInsert(t *testing.T) { repoModel := &models.Repository{} require.NoError(t, gofakeit.Struct(repoModel)) repoModel.Name = "aaabbbb" + repoModel.Visible = true newRepo, err := repo.Insert(ctx, repoModel) require.NoError(t, err) require.NotEqual(t, uuid.Nil, newRepo.ID) @@ -70,6 +83,7 @@ func TestRepositoryRepoInsert(t *testing.T) { require.NoError(t, gofakeit.Struct(secModel)) secModel.CreatorID = repoModel.CreatorID secModel.Name = "adabbeb" + secModel.Visible = true secRepo, err := repo.Insert(ctx, secModel) require.NoError(t, err) require.NotEqual(t, uuid.Nil, secRepo.ID) @@ -81,59 +95,59 @@ func TestRepositoryRepoInsert(t *testing.T) { { //exact adabbeb - repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("adabbeb", models.PrefixMatch)) + repos, _, err := repo.List(ctx, models.NewListRepoParams().SetVisible(true).SetCreatorID(secModel.CreatorID).SetName("adabbeb", models.PrefixMatch)) require.NoError(t, err) require.Len(t, repos, 1) } { //prefix a - repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("a", models.PrefixMatch)) + repos, _, err := repo.List(ctx, models.NewListRepoParams().SetVisible(true).SetCreatorID(secModel.CreatorID).SetName("a", models.PrefixMatch)) require.NoError(t, err) require.Len(t, repos, 2) } { //subfix b - repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("b", models.SuffixMatch)) + repos, _, err := repo.List(ctx, models.NewListRepoParams().SetVisible(true).SetCreatorID(secModel.CreatorID).SetName("b", models.SuffixMatch)) require.NoError(t, err) require.Len(t, repos, 2) } { //like ab - repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("ab", models.LikeMatch)) + repos, _, err := repo.List(ctx, models.NewListRepoParams().SetVisible(true).SetCreatorID(secModel.CreatorID).SetName("ab", models.LikeMatch)) require.NoError(t, err) require.Len(t, repos, 2) } { //like ab - repos, _, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetName("adabbeb", models.LikeMatch)) + repos, _, err := repo.List(ctx, models.NewListRepoParams().SetVisible(true).SetCreatorID(secModel.CreatorID).SetName("adabbeb", models.LikeMatch)) require.NoError(t, err) require.Len(t, repos, 1) } { //amount 1 - repos, hasMore, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetAmount(1)) + repos, hasMore, err := repo.List(ctx, models.NewListRepoParams().SetVisible(true).SetCreatorID(secModel.CreatorID).SetAmount(1)) require.NoError(t, err) require.True(t, hasMore) require.Len(t, repos, 1) } { //amount 2 - repos, hasMore, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetAmount(2)) + repos, hasMore, err := repo.List(ctx, models.NewListRepoParams().SetVisible(true).SetCreatorID(secModel.CreatorID).SetAmount(2)) require.NoError(t, err) require.True(t, hasMore) require.Len(t, repos, 2) } { //amount 3 - repos, hasMore, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetAmount(3)) + repos, hasMore, err := repo.List(ctx, models.NewListRepoParams().SetVisible(true).SetCreatorID(secModel.CreatorID).SetAmount(3)) require.NoError(t, err) require.False(t, hasMore) require.Len(t, repos, 2) } { //after - repos, hasMore, err := repo.List(ctx, models.NewListRepoParams().SetCreatorID(secModel.CreatorID).SetAfter(time.Now()).SetAmount(1)) + repos, hasMore, err := repo.List(ctx, models.NewListRepoParams().SetVisible(true).SetCreatorID(secModel.CreatorID).SetAfter(time.Now()).SetAmount(1)) require.NoError(t, err) require.True(t, hasMore) require.Len(t, repos, 1) From a264311852ed87ed3dd1d4572bd9954796c5114e Mon Sep 17 00:00:00 2001 From: taoshengshi Date: Thu, 7 Mar 2024 11:25:39 +0800 Subject: [PATCH 189/210] update readme doc to the latest research and community status. (#134) * update readme doc to the latest research and community status. --- README.md | 50 +++++++++++++++++++++++++++++++++++++++-- docs/logo/jiaozifs.png | Bin 0 -> 32053 bytes 2 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 docs/logo/jiaozifs.png diff --git a/README.md b/README.md index ac9004a4..bd3a026d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# JiaoziFS +# JiaoziFS (JZFS) A version control file system for data centric applications & teams.

@@ -8,6 +8,40 @@ A version control file system for data centric applications & teams.

+ + +---- +### What is JiaoziFS? +JiaoziFS is an industry-leading **Data-Centric Version Control** File System, helps ensure Responsible AI Engineering by improving **Data Versioning**, **Provenance**, and **Reproducibility**. + +Note: +* The name Jiaozi pays tribute to the world's earliest paper money: [Song Dynasty Jiaozi](https://en.wikipedia.org/wiki/Jiaozi_(currency)). +* JiaoziFS is yet another implementation of [IPFS (InterPlanetary File System)](https://ipfs.tech/) as JiaoziFS will be compatible with the [implementation requirements](https://specs.ipfs.tech/architecture/principles/#ipfs-implementation-requirements) of IPFS. +* As a filesystem of data versioning at scale, although JiaoziFS is built for machine learning, It has a wide range of use scenarios (refer A Universe of Uses) and can be seamlessly integrated into all your data stack. + +Data-centric AI is about the practice of iterating and collaborating on data, used to build AI systems, programmatically. Machine learning pioneer Andrew Ng [argues that focusing on the quality of data fueling AI systems will help unlock its full power](https://youtu.be/TU6u_T-s68Y). + +---- +### Why JiaoziFS? +In production systems with machine learning components, updates and experiments are frequent. New updates to models(data products) may be released every day or every few minutes, and different users may see the results of different models as part of A/B experiments or canary releases. + +* **Version Everything**: Data scientists are often criticized for being less disciplined with versioning their experiments(versioning of data, pipeline, code, and models), especially when using computational notebooks. +* **Track Data Provenance**: This applies to all processing steps in an AI/ML pipeline, including data collection/acquisition, data merging, data cleaning, feature extraction, learning, or deployment. +* **Reproducibility**: A final question of AI/ML that is often relevant for debugging, audits, and also science more broadly is to what degree data, models, and decisions can be reproduced. + +---- +### A Universe of Uses +JiaoziFS's versatility shines across different industries – making it the multi-purpose tool for the **data centric applications and teams**. + +* **Enterprise DataHub & Data Collaboration**: Depending on your operating scale, you may even be managing multiple team members, who may be spread across different locations. JiaoziFS enable Collaborative Datasets Version Management at Scale,Share & collaborate easily: Instantly share insights and co-edit with your team. +* **DataOps & Data Products & Data Mesh**: Augmenting Enterprise Data Development and Operations,JiaoziFS ensures Responsible DataOps/AIOps/MLOps by improving Data Versioning, Provenance, and Reproducibility. JiaoziFS makes a fusion of data science and product development and allows data to be containerized into shareable, tradeable, and trackable assets(data products or data NFTs). Versioning data products in a maturing Data Mesh environment via standard processes, data consumers can be informed about both breaking and non-breaking changes in a data product, as well as retirement of data products. +* **Digital Twins for Manufacturing**: Developing digital twins for manufacturing involves managing tons of large files and multiple iterations of a project. All of the data collected and created in the digital twin process (and there is a lot of it) needs to be managed carefully. JiaoziFS allows you to manage changes to files over time and store these modifications in a database. + +---- +### Spec +[JiaoziFS Specification](https://github.com/jiaozifs/Spec) + +---- ### Basic Build And Usage #### Requirement @@ -35,6 +69,18 @@ After following the above steps, you should be able to see an executable file na ```bash docker run -v :/app -p 34913:34913 gitdatateam/jzfs:latest --db "postgres://:@192.168.1.16:5432/jiaozifs?sslmode=disable" --bs_path /app/data --listen http://0.0.0.0:34913 --config /app/config.toml ``` -## License + +---- +### Cloud +[Try without installing](https://cloud.jiaozifs.com) + +---- +### Contributors + + + +---- +### License Dual-licensed under [MIT](https://github.com/jiaozifs/jiaozifs/blob/main/LICENSE-MIT) + [Apache 2.0](https://github.com/jiaozifs/jiaozifs/blob/main/LICENSE-APACHE) + diff --git a/docs/logo/jiaozifs.png b/docs/logo/jiaozifs.png new file mode 100644 index 0000000000000000000000000000000000000000..88a202f99ebde27b27d06477dc7a7a9c73d5e713 GIT binary patch literal 32053 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

A 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 H5d6OY{xCT; literal 0 HcmV?d00001 From fbfb59cc6d472886115661f9181232e42a16e223 Mon Sep 17 00:00:00 2001 From: taoshengshi Date: Thu, 7 Mar 2024 11:45:02 +0800 Subject: [PATCH 190/210] fix log picture (#135) * update readme doc to the latest research and community status. * minor fix * fix logo picture --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bd3a026d..1265b7b7 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A version control file system for data centric applications & teams.

- + ---- ### What is JiaoziFS? From 054a5cec497918379acab1db91152a4edd9d3c79 Mon Sep 17 00:00:00 2001 From: taoshengshi Date: Fri, 8 Mar 2024 16:00:24 +0800 Subject: [PATCH 191/210] update usecases (#137) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1265b7b7..0525c0f0 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,8 @@ JiaoziFS's versatility shines across different industries – making it the mult * **Enterprise DataHub & Data Collaboration**: Depending on your operating scale, you may even be managing multiple team members, who may be spread across different locations. JiaoziFS enable Collaborative Datasets Version Management at Scale,Share & collaborate easily: Instantly share insights and co-edit with your team. * **DataOps & Data Products & Data Mesh**: Augmenting Enterprise Data Development and Operations,JiaoziFS ensures Responsible DataOps/AIOps/MLOps by improving Data Versioning, Provenance, and Reproducibility. JiaoziFS makes a fusion of data science and product development and allows data to be containerized into shareable, tradeable, and trackable assets(data products or data NFTs). Versioning data products in a maturing Data Mesh environment via standard processes, data consumers can be informed about both breaking and non-breaking changes in a data product, as well as retirement of data products. -* **Digital Twins for Manufacturing**: Developing digital twins for manufacturing involves managing tons of large files and multiple iterations of a project. All of the data collected and created in the digital twin process (and there is a lot of it) needs to be managed carefully. JiaoziFS allows you to manage changes to files over time and store these modifications in a database. - +* **Industrial Digital Twin**: Developing digital twins for manufacturing involves managing tons of large files and multiple iterations of a project. All of the data collected and created in the digital twin process (and there is a lot of it) needs to be managed carefully. JiaoziFS allows you to manage changes to files over time and store these modifications in a database. +* **Data Lake Management**: Data lakes are dynamic. New files and new versions of ex- isting files enter the lake at the ingestion stage. Additionally, extractors can evolve over time and generate new versions of raw data. As a result, data lake versioning is a cross-cutting concern across all stages of a data lake. Of course vanilla dis- tributed file systems are not adequate for versioning-related operations. For example, simply storing all versions may be too costly for large datasets, and without a good version manager, just using filenames to track versions can be error-prone. In a data lake, for which there are usually many users, it is even more important to clearly maintain correct versions being used and evolving across different users. Furthermore, as the number of versions increases, efficiently and cost-effectively providing storage and retrieval of versions is going to be an important feature of a successful data lake system. ---- ### Spec [JiaoziFS Specification](https://github.com/jiaozifs/Spec) From ab8ebfd51109317449bfdf95cd52f07ae50f338e Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Sat, 9 Mar 2024 14:23:44 +0800 Subject: [PATCH 192/210] chore: rename package (#138) * chore: rename package * chore: fix lint and test --- README.md | 10 ++--- api/aksk_opts.go | 2 +- api/api_impl/impl.go | 4 +- api/api_impl/server.go | 14 +++--- api/custom_response.go | 4 +- api/custom_response_test.go | 4 +- auth/aksk.go | 4 +- auth/auth_middleware.go | 8 ++-- auth/authenticator.go | 2 +- auth/context.go | 2 +- auth/crypt/encryption_test.go | 2 +- auth/rbac/arn.go | 4 +- auth/rbac/arn_test.go | 2 +- auth/rbac/rbac.go | 12 ++--- auth/rbac/rbac_test.go | 10 ++--- auth/rbac/statements.go | 2 +- auth/rbac/wildcard/match_test.go | 2 +- auth/session_store.go | 4 +- block/azure/adapter.go | 8 ++-- block/azure/adapter_test.go | 6 +-- block/azure/client_cache.go | 2 +- block/azure/client_test.go | 4 +- block/azure/multipart_block_writer.go | 5 +-- block/azure/walker.go | 2 +- block/blocktest/adapter.go | 2 +- block/factory/build.go | 18 ++++---- block/gs/adapter.go | 2 +- block/gs/adapter_test.go | 4 +- block/gs/walker.go | 2 +- block/ipfs/adapter.go | 4 +- block/ipfs/walker.go | 2 +- block/local/adapter.go | 7 ++- block/local/adapter_test.go | 6 +-- block/local/etag_test.go | 2 +- block/local/walker.go | 4 +- block/mem/adapter.go | 2 +- block/namespace_test.go | 2 +- block/path_test.go | 2 +- block/s3/adapter.go | 7 ++- block/s3/adapter_test.go | 6 +-- block/s3/client_cache.go | 2 +- block/s3/client_cache_test.go | 6 +-- block/s3/walker.go | 2 +- block/transient/adapter.go | 2 +- chart/Chart.yaml | 4 +- cmd/daemon.go | 26 +++++------ cmd/helper.go | 4 +- cmd/init.go | 18 ++++---- cmd/version.go | 6 +-- config/blockstore.go | 2 +- controller/aksk_ctl.go | 14 +++--- controller/base_ctl.go | 6 +-- controller/branch_ctl.go | 20 ++++----- controller/commit_ctl.go | 18 ++++---- controller/common_ctl.go | 8 ++-- controller/group_ctl.go | 12 ++--- controller/helper.go | 6 +-- controller/member_ctl.go | 10 ++--- controller/merge_request_ctl.go | 26 +++++------ controller/object_ctl.go | 26 +++++------ controller/repository_ctl.go | 44 +++++++++---------- controller/user_ctl.go | 18 ++++---- controller/wip_ctl.go | 18 ++++---- docs/rbac.md | 2 +- examples/aksk.go | 6 +-- fx_opt/options.go | 4 +- go.mod | 4 +- integrationtest/aksk_test.go | 14 +++--- integrationtest/branch_test.go | 10 ++--- integrationtest/commit_test.go | 16 +++---- integrationtest/group_test.go | 4 +- integrationtest/helper_test.go | 10 ++--- integrationtest/member_test.go | 8 ++-- integrationtest/merge_request_test.go | 12 ++--- integrationtest/objects_test.go | 10 ++--- integrationtest/public_repo_test.go | 8 ++-- integrationtest/repo_test.go | 16 +++---- integrationtest/status_test.go | 2 +- integrationtest/user_test.go | 10 ++--- integrationtest/wip_object_test.go | 18 ++++---- integrationtest/wip_test.go | 8 ++-- main.go | 4 +- makefile | 2 +- models/aksk_test.go | 4 +- models/branch.go | 2 +- models/branch_test.go | 6 +-- models/commit.go | 2 +- models/commit_test.go | 4 +- models/members_test.go | 6 +-- models/merge_request_test.go | 7 ++- .../migrations/20210505110026_init_project.go | 4 +- models/models.go | 4 +- models/models_test.go | 4 +- models/rbacmodel/actions_test.go | 2 +- models/rbacmodel/group_test.go | 6 +-- models/rbacmodel/policies_test.go | 4 +- models/rbacmodel/user_group_test.go | 4 +- models/repo.go | 4 +- models/repo_test.go | 4 +- models/repository_test.go | 4 +- models/tag.go | 2 +- models/tag_test.go | 4 +- models/tree.go | 4 +- models/tree_test.go | 8 ++-- models/user_test.go | 4 +- models/wip.go | 2 +- models/wip_test.go | 6 +-- testhelper/pg.go | 6 +-- utils/httputil/endpoints.go | 2 +- utils/httputil/range_test.go | 2 +- utils/pathutil/hash_path.go | 2 +- utils/pathutil/hash_path_test.go | 2 +- version/latest.go | 2 +- version/latest_test.go | 2 +- versionmgr/adapter_from_config.go | 6 +-- versionmgr/changes.go | 12 ++--- versionmgr/changes_test.go | 6 +-- versionmgr/commit_node.go | 4 +- versionmgr/commit_node_test.go | 6 +-- versionmgr/commit_walker.go | 2 +- versionmgr/commit_walker_bfs.go | 2 +- versionmgr/commit_walker_bfs_filtered.go | 2 +- versionmgr/commit_walker_bfs_filtered_test.go | 6 +-- versionmgr/commit_walker_bfs_test.go | 6 +-- versionmgr/commit_walker_ctime.go | 2 +- versionmgr/commit_walker_ctime_test.go | 6 +-- versionmgr/commit_walker_test.go | 6 +-- versionmgr/conflict.go | 4 +- versionmgr/conflict_test.go | 2 +- versionmgr/merge_base_test.go | 6 +-- versionmgr/merkletrie/change.go | 2 +- versionmgr/merkletrie/change_test.go | 6 +-- versionmgr/merkletrie/difftree.go | 2 +- versionmgr/merkletrie/difftree_test.go | 4 +- versionmgr/merkletrie/doubleiter.go | 2 +- versionmgr/merkletrie/internal/frame/frame.go | 2 +- .../merkletrie/internal/frame/frame_test.go | 4 +- versionmgr/merkletrie/internal/fsnoder/dir.go | 2 +- .../merkletrie/internal/fsnoder/dir_test.go | 2 +- .../merkletrie/internal/fsnoder/file.go | 2 +- .../merkletrie/internal/fsnoder/file_test.go | 2 +- versionmgr/merkletrie/internal/fsnoder/new.go | 2 +- .../merkletrie/internal/fsnoder/new_test.go | 2 +- versionmgr/merkletrie/iter.go | 4 +- versionmgr/merkletrie/iter_test.go | 6 +-- versionmgr/tree_node.go | 4 +- versionmgr/work_repo.go | 18 ++++---- versionmgr/work_repo_diff_test.go | 10 ++--- versionmgr/work_repo_merge_test.go | 8 ++-- versionmgr/work_repo_test.go | 14 +++--- versionmgr/worktree.go | 8 ++-- versionmgr/worktree_test.go | 6 +-- 152 files changed, 481 insertions(+), 485 deletions(-) diff --git a/README.md b/README.md index 0525c0f0..060e5178 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ A version control file system for data centric applications & teams.

- - + +

- + ---- ### What is JiaoziFS? @@ -52,7 +52,7 @@ JiaoziFS's versatility shines across different industries – making it the mult 1. clone and build ```bash -git clone https://github.com/jiaozifs/jiaozifs.git +git clone https://github.com/GitDataAI/jiaozifs.git cd jiaozifs make build ``` @@ -82,5 +82,5 @@ docker run -v :/app -p 34913:34913 gitdatateam/jzfs:latest --db "postgres ---- ### License -Dual-licensed under [MIT](https://github.com/jiaozifs/jiaozifs/blob/main/LICENSE-MIT) + [Apache 2.0](https://github.com/jiaozifs/jiaozifs/blob/main/LICENSE-APACHE) +Dual-licensed under [MIT](https://github.com/GitDataAI/jiaozifs/blob/main/LICENSE-MIT) + [Apache 2.0](https://github.com/GitDataAI/jiaozifs/blob/main/LICENSE-APACHE) diff --git a/api/aksk_opts.go b/api/aksk_opts.go index d1dc41e7..2b77be9a 100644 --- a/api/aksk_opts.go +++ b/api/aksk_opts.go @@ -4,7 +4,7 @@ import ( "context" "net/http" - "github.com/jiaozifs/jiaozifs/auth/aksk" + "github.com/GitDataAI/jiaozifs/auth/aksk" ) func AkSkOption(ak, sk string) ClientOption { diff --git a/api/api_impl/impl.go b/api/api_impl/impl.go index 41f8794a..46d00440 100644 --- a/api/api_impl/impl.go +++ b/api/api_impl/impl.go @@ -1,8 +1,8 @@ package apiimpl import ( - "github.com/jiaozifs/jiaozifs/api" - "github.com/jiaozifs/jiaozifs/controller" + "github.com/GitDataAI/jiaozifs/api" + "github.com/GitDataAI/jiaozifs/controller" "go.uber.org/fx" ) diff --git a/api/api_impl/server.go b/api/api_impl/server.go index db4c8a07..c861093f 100644 --- a/api/api_impl/server.go +++ b/api/api_impl/server.go @@ -9,13 +9,18 @@ import ( "net/url" "strings" - "github.com/jiaozifs/jiaozifs/auth/aksk" + "github.com/GitDataAI/jiaozifs/auth/aksk" "github.com/hellofresh/health-go/v5" "github.com/getkin/kin-openapi/openapi3" "github.com/getkin/kin-openapi/routers/gorillamux" + "github.com/GitDataAI/jiaozifs/api" + "github.com/GitDataAI/jiaozifs/auth" + "github.com/GitDataAI/jiaozifs/auth/crypt" + "github.com/GitDataAI/jiaozifs/config" + "github.com/GitDataAI/jiaozifs/models" "github.com/MadAppGang/httplog" "github.com/flowchartsman/swaggerui" "github.com/getkin/kin-openapi/openapi3filter" @@ -23,11 +28,6 @@ import ( "github.com/go-chi/chi/v5" "github.com/gorilla/sessions" logging "github.com/ipfs/go-log/v2" - "github.com/jiaozifs/jiaozifs/api" - "github.com/jiaozifs/jiaozifs/auth" - "github.com/jiaozifs/jiaozifs/auth/crypt" - "github.com/jiaozifs/jiaozifs/config" - "github.com/jiaozifs/jiaozifs/models" "github.com/rs/cors" "go.uber.org/fx" ) @@ -109,7 +109,7 @@ func SetupAPI(lc fx.Lifecycle, }() lc.Append(fx.Hook{ - OnStop: func(ctx context.Context) error { + OnStop: func(_ context.Context) error { return listener.Close() }, }) diff --git a/api/custom_response.go b/api/custom_response.go index a273a743..756f2007 100644 --- a/api/custom_response.go +++ b/api/custom_response.go @@ -5,8 +5,8 @@ import ( "errors" "net/http" - "github.com/jiaozifs/jiaozifs/auth" - "github.com/jiaozifs/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/auth" + "github.com/GitDataAI/jiaozifs/models" ) type JiaozifsResponse struct { diff --git a/api/custom_response_test.go b/api/custom_response_test.go index a7461177..a9d42eae 100644 --- a/api/custom_response_test.go +++ b/api/custom_response_test.go @@ -5,9 +5,9 @@ import ( "net/http" "testing" - "github.com/jiaozifs/jiaozifs/auth" + "github.com/GitDataAI/jiaozifs/auth" - "github.com/jiaozifs/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/models" "go.uber.org/mock/gomock" ) diff --git a/auth/aksk.go b/auth/aksk.go index 4888516a..0c6badd3 100644 --- a/auth/aksk.go +++ b/auth/aksk.go @@ -3,8 +3,8 @@ package auth import ( "context" - "github.com/jiaozifs/jiaozifs/auth/aksk" - "github.com/jiaozifs/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/auth/aksk" + "github.com/GitDataAI/jiaozifs/models" ) var _ aksk.SkGetter = (*SkGetter)(nil) diff --git a/auth/auth_middleware.go b/auth/auth_middleware.go index 7550b451..8cd01cc9 100644 --- a/auth/auth_middleware.go +++ b/auth/auth_middleware.go @@ -9,18 +9,18 @@ import ( logging "github.com/ipfs/go-log/v2" - "github.com/jiaozifs/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/utils" - "github.com/jiaozifs/jiaozifs/auth/aksk" + "github.com/GitDataAI/jiaozifs/auth/aksk" "github.com/golang-jwt/jwt/v5" + "github.com/GitDataAI/jiaozifs/auth/crypt" + "github.com/GitDataAI/jiaozifs/models" "github.com/getkin/kin-openapi/openapi3" "github.com/getkin/kin-openapi/routers" "github.com/getkin/kin-openapi/routers/legacy" "github.com/gorilla/sessions" - "github.com/jiaozifs/jiaozifs/auth/crypt" - "github.com/jiaozifs/jiaozifs/models" ) const ( diff --git a/auth/authenticator.go b/auth/authenticator.go index 5248daa2..12d0adb2 100644 --- a/auth/authenticator.go +++ b/auth/authenticator.go @@ -3,7 +3,7 @@ package auth import ( "context" - "github.com/jiaozifs/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/models" "golang.org/x/crypto/bcrypt" ) diff --git a/auth/context.go b/auth/context.go index 230da7a4..2601c00c 100644 --- a/auth/context.go +++ b/auth/context.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/jiaozifs/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/models" ) var ErrUserNotFound = fmt.Errorf("UserNotFound") diff --git a/auth/crypt/encryption_test.go b/auth/crypt/encryption_test.go index f5e3906d..891e8b4a 100644 --- a/auth/crypt/encryption_test.go +++ b/auth/crypt/encryption_test.go @@ -5,7 +5,7 @@ import ( "fmt" "testing" - "github.com/jiaozifs/jiaozifs/auth/crypt" + "github.com/GitDataAI/jiaozifs/auth/crypt" ) func TestSecretStore_Encrypt(t *testing.T) { diff --git a/auth/rbac/arn.go b/auth/rbac/arn.go index 18519af5..9a63bdcc 100644 --- a/auth/rbac/arn.go +++ b/auth/rbac/arn.go @@ -4,8 +4,8 @@ import ( "errors" "strings" - "github.com/jiaozifs/jiaozifs/auth/rbac/wildcard" - "github.com/jiaozifs/jiaozifs/models/rbacmodel" + "github.com/GitDataAI/jiaozifs/auth/rbac/wildcard" + "github.com/GitDataAI/jiaozifs/models/rbacmodel" ) var ( diff --git a/auth/rbac/arn_test.go b/auth/rbac/arn_test.go index a2f0fc07..0e7d1743 100644 --- a/auth/rbac/arn_test.go +++ b/auth/rbac/arn_test.go @@ -3,7 +3,7 @@ package rbac_test import ( "testing" - "github.com/jiaozifs/jiaozifs/auth/rbac" + "github.com/GitDataAI/jiaozifs/auth/rbac" ) func TestParseARN(t *testing.T) { diff --git a/auth/rbac/rbac.go b/auth/rbac/rbac.go index e2eab151..16064db0 100644 --- a/auth/rbac/rbac.go +++ b/auth/rbac/rbac.go @@ -7,12 +7,12 @@ import ( "sync" "time" - "github.com/jiaozifs/jiaozifs/auth/rbac/wildcard" + "github.com/GitDataAI/jiaozifs/auth/rbac/wildcard" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/models/rbacmodel" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/models/rbacmodel" ) var ErrInsufficientPermissions = fmt.Errorf("permission not enough") @@ -195,18 +195,18 @@ func (s *RbacAuth) InitRbac(ctx context.Context, adminUser *models.User) error { } func (s *RbacAuth) addGroupPolicy(ctx context.Context, repo models.IRepo, groupName string, policies ...*rbacmodel.Policy) (*rbacmodel.Group, error) { - var policyIds []uuid.UUID + var policyIDs []uuid.UUID for _, policy := range policies { _, err := repo.PolicyRepo().Insert(ctx, policy) if err != nil { return nil, err } - policyIds = append(policyIds, policy.ID) + policyIDs = append(policyIDs, policy.ID) } return repo.GroupRepo().Insert(ctx, &rbacmodel.Group{ Name: groupName, - Policies: policyIds, + Policies: policyIDs, CreatedAt: time.Now(), UpdatedAt: time.Now(), }) diff --git a/auth/rbac/rbac_test.go b/auth/rbac/rbac_test.go index 3a085874..d3f64c92 100644 --- a/auth/rbac/rbac_test.go +++ b/auth/rbac/rbac_test.go @@ -9,13 +9,13 @@ import ( "github.com/stretchr/testify/require" - "github.com/jiaozifs/jiaozifs/auth" - "github.com/jiaozifs/jiaozifs/models/rbacmodel" + "github.com/GitDataAI/jiaozifs/auth" + "github.com/GitDataAI/jiaozifs/models/rbacmodel" - "github.com/jiaozifs/jiaozifs/auth/rbac" - "github.com/jiaozifs/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/auth/rbac" + "github.com/GitDataAI/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/GitDataAI/jiaozifs/testhelper" ) func TestNewRbac(t *testing.T) { diff --git a/auth/rbac/statements.go b/auth/rbac/statements.go index 2232d15c..d5d4125a 100644 --- a/auth/rbac/statements.go +++ b/auth/rbac/statements.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" - "github.com/jiaozifs/jiaozifs/models/rbacmodel" + "github.com/GitDataAI/jiaozifs/models/rbacmodel" ) var ( diff --git a/auth/rbac/wildcard/match_test.go b/auth/rbac/wildcard/match_test.go index d2d38aa7..d986bfeb 100644 --- a/auth/rbac/wildcard/match_test.go +++ b/auth/rbac/wildcard/match_test.go @@ -19,7 +19,7 @@ package wildcard_test import ( "testing" - "github.com/jiaozifs/jiaozifs/auth/rbac/wildcard" + "github.com/GitDataAI/jiaozifs/auth/rbac/wildcard" ) // TestMatch - Tests validate the logic of wild card matching. diff --git a/auth/session_store.go b/auth/session_store.go index caae43eb..de118299 100644 --- a/auth/session_store.go +++ b/auth/session_store.go @@ -3,9 +3,9 @@ package auth import ( "encoding/hex" + "github.com/GitDataAI/jiaozifs/auth/crypt" + "github.com/GitDataAI/jiaozifs/config" "github.com/gorilla/sessions" - "github.com/jiaozifs/jiaozifs/auth/crypt" - "github.com/jiaozifs/jiaozifs/config" ) func NewSessionStore(secretStrore crypt.SecretStore) sessions.Store { diff --git a/block/azure/adapter.go b/block/azure/adapter.go index a02c56be..fd7a2760 100644 --- a/block/azure/adapter.go +++ b/block/azure/adapter.go @@ -15,10 +15,10 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blockblob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas" - "github.com/jiaozifs/jiaozifs/block" - "github.com/jiaozifs/jiaozifs/block/params" - "github.com/jiaozifs/jiaozifs/utils" - "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/GitDataAI/jiaozifs/block" + "github.com/GitDataAI/jiaozifs/block/params" + "github.com/GitDataAI/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/utils/hash" ) const ( diff --git a/block/azure/adapter_test.go b/block/azure/adapter_test.go index 492808c8..e6b46976 100644 --- a/block/azure/adapter_test.go +++ b/block/azure/adapter_test.go @@ -6,9 +6,9 @@ import ( "regexp" "testing" - "github.com/jiaozifs/jiaozifs/block/azure" - "github.com/jiaozifs/jiaozifs/block/blocktest" - "github.com/jiaozifs/jiaozifs/block/params" + "github.com/GitDataAI/jiaozifs/block/azure" + "github.com/GitDataAI/jiaozifs/block/blocktest" + "github.com/GitDataAI/jiaozifs/block/params" "github.com/stretchr/testify/require" ) diff --git a/block/azure/client_cache.go b/block/azure/client_cache.go index c4f73f36..1873566c 100644 --- a/block/azure/client_cache.go +++ b/block/azure/client_cache.go @@ -12,8 +12,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/service" + "github.com/GitDataAI/jiaozifs/block/params" lru "github.com/hnlq715/golang-lru" - "github.com/jiaozifs/jiaozifs/block/params" "github.com/puzpuzpuz/xsync" ) diff --git a/block/azure/client_test.go b/block/azure/client_test.go index 27bfa6b3..19701d25 100644 --- a/block/azure/client_test.go +++ b/block/azure/client_test.go @@ -4,8 +4,8 @@ import ( "net/url" "testing" - "github.com/jiaozifs/jiaozifs/block" - "github.com/jiaozifs/jiaozifs/block/azure" + "github.com/GitDataAI/jiaozifs/block" + "github.com/GitDataAI/jiaozifs/block/azure" "github.com/stretchr/testify/require" ) diff --git a/block/azure/multipart_block_writer.go b/block/azure/multipart_block_writer.go index b2bcf3c9..bf9298e4 100644 --- a/block/azure/multipart_block_writer.go +++ b/block/azure/multipart_block_writer.go @@ -11,15 +11,14 @@ import ( "strconv" "strings" - "github.com/jiaozifs/jiaozifs/utils/hash" - "github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blockblob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container" + "github.com/GitDataAI/jiaozifs/block" + "github.com/GitDataAI/jiaozifs/utils/hash" "github.com/google/uuid" logging "github.com/ipfs/go-log/v2" - "github.com/jiaozifs/jiaozifs/block" ) var log = logging.Logger("azure") diff --git a/block/azure/walker.go b/block/azure/walker.go index 1cf577f1..4211cbf2 100644 --- a/block/azure/walker.go +++ b/block/azure/walker.go @@ -11,7 +11,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/service" - "github.com/jiaozifs/jiaozifs/block" + "github.com/GitDataAI/jiaozifs/block" ) const DirectoryBlobMetadataKey = "hdi_isfolder" diff --git a/block/blocktest/adapter.go b/block/blocktest/adapter.go index 5ff1ba48..6f8e7dd8 100644 --- a/block/blocktest/adapter.go +++ b/block/blocktest/adapter.go @@ -12,8 +12,8 @@ import ( "strings" "testing" + "github.com/GitDataAI/jiaozifs/block" "github.com/go-test/deep" - "github.com/jiaozifs/jiaozifs/block" "github.com/stretchr/testify/require" "github.com/thanhpk/randstr" ) diff --git a/block/factory/build.go b/block/factory/build.go index d7322ccf..a3516a48 100644 --- a/block/factory/build.go +++ b/block/factory/build.go @@ -4,19 +4,19 @@ import ( "context" "fmt" - "github.com/jiaozifs/jiaozifs/block/ipfs" + "github.com/GitDataAI/jiaozifs/block/ipfs" "cloud.google.com/go/storage" + "github.com/GitDataAI/jiaozifs/block" + "github.com/GitDataAI/jiaozifs/block/azure" + "github.com/GitDataAI/jiaozifs/block/gs" + "github.com/GitDataAI/jiaozifs/block/local" + "github.com/GitDataAI/jiaozifs/block/mem" + "github.com/GitDataAI/jiaozifs/block/params" + s3a "github.com/GitDataAI/jiaozifs/block/s3" + "github.com/GitDataAI/jiaozifs/block/transient" "github.com/aws/aws-sdk-go-v2/service/s3" logging "github.com/ipfs/go-log/v2" - "github.com/jiaozifs/jiaozifs/block" - "github.com/jiaozifs/jiaozifs/block/azure" - "github.com/jiaozifs/jiaozifs/block/gs" - "github.com/jiaozifs/jiaozifs/block/local" - "github.com/jiaozifs/jiaozifs/block/mem" - "github.com/jiaozifs/jiaozifs/block/params" - s3a "github.com/jiaozifs/jiaozifs/block/s3" - "github.com/jiaozifs/jiaozifs/block/transient" "golang.org/x/oauth2/google" "google.golang.org/api/option" ) diff --git a/block/gs/adapter.go b/block/gs/adapter.go index 17ef37ff..b529094d 100644 --- a/block/gs/adapter.go +++ b/block/gs/adapter.go @@ -12,8 +12,8 @@ import ( "time" "cloud.google.com/go/storage" + "github.com/GitDataAI/jiaozifs/block" logging "github.com/ipfs/go-log/v2" - "github.com/jiaozifs/jiaozifs/block" "google.golang.org/api/iterator" ) diff --git a/block/gs/adapter_test.go b/block/gs/adapter_test.go index bbab1e74..2666292a 100644 --- a/block/gs/adapter_test.go +++ b/block/gs/adapter_test.go @@ -5,8 +5,8 @@ import ( "regexp" "testing" - "github.com/jiaozifs/jiaozifs/block/blocktest" - "github.com/jiaozifs/jiaozifs/block/gs" + "github.com/GitDataAI/jiaozifs/block/blocktest" + "github.com/GitDataAI/jiaozifs/block/gs" "github.com/stretchr/testify/require" ) diff --git a/block/gs/walker.go b/block/gs/walker.go index 9be907e4..d5a73264 100644 --- a/block/gs/walker.go +++ b/block/gs/walker.go @@ -9,7 +9,7 @@ import ( "strings" "cloud.google.com/go/storage" - "github.com/jiaozifs/jiaozifs/block" + "github.com/GitDataAI/jiaozifs/block" "google.golang.org/api/iterator" ) diff --git a/block/ipfs/adapter.go b/block/ipfs/adapter.go index 3c0ff397..43ad4dea 100644 --- a/block/ipfs/adapter.go +++ b/block/ipfs/adapter.go @@ -20,10 +20,10 @@ import ( "github.com/ipfs/boxo/files" + "github.com/GitDataAI/jiaozifs/block" + "github.com/GitDataAI/jiaozifs/utils/hash" "github.com/google/uuid" "github.com/ipfs/kubo/client/rpc" - "github.com/jiaozifs/jiaozifs/block" - "github.com/jiaozifs/jiaozifs/utils/hash" ma "github.com/multiformats/go-multiaddr" ) diff --git a/block/ipfs/walker.go b/block/ipfs/walker.go index 8d84e99f..109964ed 100644 --- a/block/ipfs/walker.go +++ b/block/ipfs/walker.go @@ -8,11 +8,11 @@ import ( "sort" "strings" + "github.com/GitDataAI/jiaozifs/block" "github.com/ipfs/boxo/path" "github.com/ipfs/kubo/client/rpc" iface "github.com/ipfs/kubo/core/coreiface" "github.com/ipfs/kubo/core/coreiface/options" - "github.com/jiaozifs/jiaozifs/block" "github.com/modern-go/reflect2" ) diff --git a/block/local/adapter.go b/block/local/adapter.go index 32740562..3bd2d9ef 100644 --- a/block/local/adapter.go +++ b/block/local/adapter.go @@ -17,11 +17,10 @@ import ( "strings" "time" - "github.com/jiaozifs/jiaozifs/utils/hash" - + "github.com/GitDataAI/jiaozifs/block" + "github.com/GitDataAI/jiaozifs/block/params" + "github.com/GitDataAI/jiaozifs/utils/hash" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/block" - "github.com/jiaozifs/jiaozifs/block/params" "golang.org/x/exp/slices" ) diff --git a/block/local/adapter_test.go b/block/local/adapter_test.go index 84a8ee63..7d516adf 100644 --- a/block/local/adapter_test.go +++ b/block/local/adapter_test.go @@ -5,9 +5,9 @@ import ( "regexp" "testing" - "github.com/jiaozifs/jiaozifs/block" - "github.com/jiaozifs/jiaozifs/block/blocktest" - "github.com/jiaozifs/jiaozifs/block/local" + "github.com/GitDataAI/jiaozifs/block" + "github.com/GitDataAI/jiaozifs/block/blocktest" + "github.com/GitDataAI/jiaozifs/block/local" "github.com/stretchr/testify/require" ) diff --git a/block/local/etag_test.go b/block/local/etag_test.go index 84612d2e..7ba4e33d 100644 --- a/block/local/etag_test.go +++ b/block/local/etag_test.go @@ -4,7 +4,7 @@ import ( "encoding/hex" "testing" - "github.com/jiaozifs/jiaozifs/block" + "github.com/GitDataAI/jiaozifs/block" ) const PartsNo = 30 diff --git a/block/local/walker.go b/block/local/walker.go index f33ef74f..999723a9 100644 --- a/block/local/walker.go +++ b/block/local/walker.go @@ -14,8 +14,8 @@ import ( "sort" "strings" - "github.com/jiaozifs/jiaozifs/block" - "github.com/jiaozifs/jiaozifs/block/params" + "github.com/GitDataAI/jiaozifs/block" + "github.com/GitDataAI/jiaozifs/block/params" gonanoid "github.com/matoous/go-nanoid/v2" ) diff --git a/block/mem/adapter.go b/block/mem/adapter.go index edf8dfa2..46542ef4 100644 --- a/block/mem/adapter.go +++ b/block/mem/adapter.go @@ -14,8 +14,8 @@ import ( "sync" "time" + "github.com/GitDataAI/jiaozifs/block" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/block" ) var ( diff --git a/block/namespace_test.go b/block/namespace_test.go index 48325440..a13bc92a 100644 --- a/block/namespace_test.go +++ b/block/namespace_test.go @@ -6,7 +6,7 @@ import ( "reflect" "testing" - "github.com/jiaozifs/jiaozifs/block" + "github.com/GitDataAI/jiaozifs/block" ) func TestResolveNamespace(t *testing.T) { diff --git a/block/path_test.go b/block/path_test.go index edcedb60..4596f8b5 100644 --- a/block/path_test.go +++ b/block/path_test.go @@ -4,8 +4,8 @@ import ( "strings" "testing" + "github.com/GitDataAI/jiaozifs/block" "github.com/davecgh/go-spew/spew" - "github.com/jiaozifs/jiaozifs/block" ) func equalStrings(a, b []string) bool { diff --git a/block/s3/adapter.go b/block/s3/adapter.go index c9980285..d4a1bf79 100644 --- a/block/s3/adapter.go +++ b/block/s3/adapter.go @@ -12,8 +12,9 @@ import ( "sync/atomic" "time" - "github.com/jiaozifs/jiaozifs/utils" - + "github.com/GitDataAI/jiaozifs/block" + "github.com/GitDataAI/jiaozifs/block/params" + "github.com/GitDataAI/jiaozifs/utils" "github.com/aws/aws-sdk-go-v2/aws" v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" "github.com/aws/aws-sdk-go-v2/config" @@ -24,8 +25,6 @@ import ( "github.com/aws/aws-sdk-go-v2/service/s3/types" "github.com/aws/smithy-go/middleware" smithyhttp "github.com/aws/smithy-go/transport/http" - "github.com/jiaozifs/jiaozifs/block" - "github.com/jiaozifs/jiaozifs/block/params" ) var ( diff --git a/block/s3/adapter_test.go b/block/s3/adapter_test.go index e50853b9..4ffb4bb4 100644 --- a/block/s3/adapter_test.go +++ b/block/s3/adapter_test.go @@ -6,9 +6,9 @@ import ( "regexp" "testing" - "github.com/jiaozifs/jiaozifs/block/blocktest" - "github.com/jiaozifs/jiaozifs/block/params" - "github.com/jiaozifs/jiaozifs/block/s3" + "github.com/GitDataAI/jiaozifs/block/blocktest" + "github.com/GitDataAI/jiaozifs/block/params" + "github.com/GitDataAI/jiaozifs/block/s3" "github.com/stretchr/testify/require" ) diff --git a/block/s3/client_cache.go b/block/s3/client_cache.go index d2ae08f6..08c9a3a2 100644 --- a/block/s3/client_cache.go +++ b/block/s3/client_cache.go @@ -4,11 +4,11 @@ import ( "context" "sync" + "github.com/GitDataAI/jiaozifs/block/params" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/feature/s3/manager" "github.com/aws/aws-sdk-go-v2/service/s3" logging "github.com/ipfs/go-log/v2" - "github.com/jiaozifs/jiaozifs/block/params" ) var log = logging.Logger("s3") diff --git a/block/s3/client_cache_test.go b/block/s3/client_cache_test.go index 3c9255e2..0c597a67 100644 --- a/block/s3/client_cache_test.go +++ b/block/s3/client_cache_test.go @@ -7,11 +7,11 @@ import ( "github.com/stretchr/testify/require" + "github.com/GitDataAI/jiaozifs/block/params" + "github.com/GitDataAI/jiaozifs/block/s3" "github.com/aws/aws-sdk-go-v2/config" awss3 "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/go-test/deep" - "github.com/jiaozifs/jiaozifs/block/params" - "github.com/jiaozifs/jiaozifs/block/s3" ) var errRegion = errors.New("failed to get region") @@ -77,7 +77,7 @@ func TestClientCache(t *testing.T) { }) }) - c.SetS3RegionGetter(func(ctx context.Context, bucket string) (string, error) { + c.SetS3RegionGetter(func(_ context.Context, bucket string) (string, error) { if actualRegionFetch[bucket] { t.Fatalf("region fetched more than once for bucket") } diff --git a/block/s3/walker.go b/block/s3/walker.go index 5a5de76d..0a5f8a3a 100644 --- a/block/s3/walker.go +++ b/block/s3/walker.go @@ -6,9 +6,9 @@ import ( "net/url" "strings" + "github.com/GitDataAI/jiaozifs/block" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/s3" - "github.com/jiaozifs/jiaozifs/block" ) type Walker struct { diff --git a/block/transient/adapter.go b/block/transient/adapter.go index 0d7497bf..05079a3b 100644 --- a/block/transient/adapter.go +++ b/block/transient/adapter.go @@ -10,8 +10,8 @@ import ( "net/url" "time" + "github.com/GitDataAI/jiaozifs/block" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/block" ) type Adapter struct{} diff --git a/chart/Chart.yaml b/chart/Chart.yaml index 30f7a4e7..3a7eed65 100644 --- a/chart/Chart.yaml +++ b/chart/Chart.yaml @@ -7,7 +7,7 @@ home: https://jiaozifs.com/ keywords: - jiaozifs sources: - - https://github.com/jiaozifs/jiaozifs - - https://github.com/jiaozifs/jiaozifs-ui + - https://github.com/GitDataAI/jiaozifs + - https://github.com/GitDataAI/jiaozifs-ui maintainers: - name: jiaozifs team diff --git a/cmd/daemon.go b/cmd/daemon.go index 5db0167a..21dc84a4 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -3,24 +3,24 @@ package cmd import ( "context" - "github.com/jiaozifs/jiaozifs/auth/rbac" + "github.com/GitDataAI/jiaozifs/auth/rbac" - "github.com/jiaozifs/jiaozifs/auth/aksk" + "github.com/GitDataAI/jiaozifs/auth/aksk" "github.com/pelletier/go-toml/v2" + apiImpl "github.com/GitDataAI/jiaozifs/api/api_impl" + "github.com/GitDataAI/jiaozifs/auth" + "github.com/GitDataAI/jiaozifs/auth/crypt" + "github.com/GitDataAI/jiaozifs/block/params" + "github.com/GitDataAI/jiaozifs/config" + "github.com/GitDataAI/jiaozifs/fx_opt" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/models/migrations" + "github.com/GitDataAI/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/version" "github.com/gorilla/sessions" logging "github.com/ipfs/go-log/v2" - apiImpl "github.com/jiaozifs/jiaozifs/api/api_impl" - "github.com/jiaozifs/jiaozifs/auth" - "github.com/jiaozifs/jiaozifs/auth/crypt" - "github.com/jiaozifs/jiaozifs/block/params" - "github.com/jiaozifs/jiaozifs/config" - "github.com/jiaozifs/jiaozifs/fx_opt" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/models/migrations" - "github.com/jiaozifs/jiaozifs/utils" - "github.com/jiaozifs/jiaozifs/version" "github.com/spf13/cobra" "github.com/uptrace/bun" ) @@ -32,7 +32,7 @@ var daemonCmd = &cobra.Command{ Use: "daemon", Short: "daemon program of jiaozifs", Long: ``, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { cfg, err := config.LoadConfig(cfgFile) if err != nil { return err diff --git a/cmd/helper.go b/cmd/helper.go index 01f65289..5c29d748 100644 --- a/cmd/helper.go +++ b/cmd/helper.go @@ -1,8 +1,8 @@ package cmd import ( - "github.com/jiaozifs/jiaozifs/api" - "github.com/jiaozifs/jiaozifs/config" + "github.com/GitDataAI/jiaozifs/api" + "github.com/GitDataAI/jiaozifs/config" ) func GetDefaultClient() (*api.Client, error) { diff --git a/cmd/init.go b/cmd/init.go index cced892e..a169784d 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -5,17 +5,17 @@ import ( "os" "time" - "github.com/jiaozifs/jiaozifs/models/migrations" + "github.com/GitDataAI/jiaozifs/models/migrations" - "github.com/jiaozifs/jiaozifs/auth/rbac" - "github.com/jiaozifs/jiaozifs/controller/validator" + "github.com/GitDataAI/jiaozifs/auth/rbac" + "github.com/GitDataAI/jiaozifs/controller/validator" - "github.com/jiaozifs/jiaozifs/auth" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/auth" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/utils" "github.com/m1/go-generate-password/generator" - "github.com/jiaozifs/jiaozifs/config" + "github.com/GitDataAI/jiaozifs/config" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -25,7 +25,7 @@ var initCmd = &cobra.Command{ Use: "init", Short: "init jiaozifs ", Long: `create default config file for jiaoozifs`, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { //protect duplicate bind flag with daemon err := viper.BindPFlag("blockstore.local.path", cmd.Flags().Lookup("bs_path")) if err != nil { @@ -39,7 +39,7 @@ var initCmd = &cobra.Command{ return viper.BindPFlag("database.connection", cmd.Flags().Lookup("db")) }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { err := config.InitConfig(cfgFile) if err != nil { return err diff --git a/cmd/version.go b/cmd/version.go index 773165af..72969e3f 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -3,8 +3,8 @@ package cmd import ( "fmt" - "github.com/jiaozifs/jiaozifs/api" - "github.com/jiaozifs/jiaozifs/version" + "github.com/GitDataAI/jiaozifs/api" + "github.com/GitDataAI/jiaozifs/version" "github.com/spf13/cobra" ) @@ -13,7 +13,7 @@ var versionCmd = &cobra.Command{ Use: "version", Short: "version of jiaozifs", Long: `jiaozifs version`, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { swagger, err := api.GetSwagger() if err != nil { return err diff --git a/config/blockstore.go b/config/blockstore.go index a7ffd2d5..777a78e7 100644 --- a/config/blockstore.go +++ b/config/blockstore.go @@ -4,7 +4,7 @@ import ( "fmt" "time" - "github.com/jiaozifs/jiaozifs/block/params" + "github.com/GitDataAI/jiaozifs/block/params" "github.com/mitchellh/go-homedir" ) diff --git a/controller/aksk_ctl.go b/controller/aksk_ctl.go index 154031a0..68f29ef0 100644 --- a/controller/aksk_ctl.go +++ b/controller/aksk_ctl.go @@ -5,18 +5,18 @@ import ( "net/http" "time" - "github.com/jiaozifs/jiaozifs/models/rbacmodel" + "github.com/GitDataAI/jiaozifs/models/rbacmodel" - "github.com/jiaozifs/jiaozifs/auth/rbac" + "github.com/GitDataAI/jiaozifs/auth/rbac" - "github.com/jiaozifs/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/utils" - aksk2 "github.com/jiaozifs/jiaozifs/auth/aksk" + aksk2 "github.com/GitDataAI/jiaozifs/auth/aksk" - "github.com/jiaozifs/jiaozifs/auth" + "github.com/GitDataAI/jiaozifs/auth" - "github.com/jiaozifs/jiaozifs/api" - "github.com/jiaozifs/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/api" + "github.com/GitDataAI/jiaozifs/models" "go.uber.org/fx" ) diff --git a/controller/base_ctl.go b/controller/base_ctl.go index 8af4751d..72708599 100644 --- a/controller/base_ctl.go +++ b/controller/base_ctl.go @@ -6,11 +6,11 @@ import ( "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/api" + "github.com/GitDataAI/jiaozifs/api" - "github.com/jiaozifs/jiaozifs/auth" + "github.com/GitDataAI/jiaozifs/auth" - "github.com/jiaozifs/jiaozifs/auth/rbac" + "github.com/GitDataAI/jiaozifs/auth/rbac" "go.uber.org/fx" ) diff --git a/controller/branch_ctl.go b/controller/branch_ctl.go index d6113680..e25a44ac 100644 --- a/controller/branch_ctl.go +++ b/controller/branch_ctl.go @@ -5,16 +5,16 @@ import ( "errors" "net/http" - "github.com/jiaozifs/jiaozifs/auth/rbac" - "github.com/jiaozifs/jiaozifs/block/params" - "github.com/jiaozifs/jiaozifs/controller/validator" - "github.com/jiaozifs/jiaozifs/models/rbacmodel" - "github.com/jiaozifs/jiaozifs/versionmgr" - - "github.com/jiaozifs/jiaozifs/api" - "github.com/jiaozifs/jiaozifs/auth" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/auth/rbac" + "github.com/GitDataAI/jiaozifs/block/params" + "github.com/GitDataAI/jiaozifs/controller/validator" + "github.com/GitDataAI/jiaozifs/models/rbacmodel" + "github.com/GitDataAI/jiaozifs/versionmgr" + + "github.com/GitDataAI/jiaozifs/api" + "github.com/GitDataAI/jiaozifs/auth" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/utils" "go.uber.org/fx" ) diff --git a/controller/commit_ctl.go b/controller/commit_ctl.go index 2085a53c..3d990ffc 100644 --- a/controller/commit_ctl.go +++ b/controller/commit_ctl.go @@ -9,15 +9,15 @@ import ( openapi_types "github.com/oapi-codegen/runtime/types" - "github.com/jiaozifs/jiaozifs/api" - "github.com/jiaozifs/jiaozifs/auth" - "github.com/jiaozifs/jiaozifs/auth/rbac" - "github.com/jiaozifs/jiaozifs/block/params" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/models/rbacmodel" - "github.com/jiaozifs/jiaozifs/utils" - "github.com/jiaozifs/jiaozifs/utils/hash" - "github.com/jiaozifs/jiaozifs/versionmgr" + "github.com/GitDataAI/jiaozifs/api" + "github.com/GitDataAI/jiaozifs/auth" + "github.com/GitDataAI/jiaozifs/auth/rbac" + "github.com/GitDataAI/jiaozifs/block/params" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/models/rbacmodel" + "github.com/GitDataAI/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/utils/hash" + "github.com/GitDataAI/jiaozifs/versionmgr" "go.uber.org/fx" ) diff --git a/controller/common_ctl.go b/controller/common_ctl.go index 4875cf59..08a32ceb 100644 --- a/controller/common_ctl.go +++ b/controller/common_ctl.go @@ -4,12 +4,12 @@ import ( "context" "net/http" + "github.com/GitDataAI/jiaozifs/api" + "github.com/GitDataAI/jiaozifs/config" + "github.com/GitDataAI/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/version" "github.com/go-openapi/swag" logging "github.com/ipfs/go-log/v2" - "github.com/jiaozifs/jiaozifs/api" - "github.com/jiaozifs/jiaozifs/config" - "github.com/jiaozifs/jiaozifs/utils" - "github.com/jiaozifs/jiaozifs/version" "go.uber.org/fx" ) diff --git a/controller/group_ctl.go b/controller/group_ctl.go index 6a35e85f..00fa0abd 100644 --- a/controller/group_ctl.go +++ b/controller/group_ctl.go @@ -4,16 +4,16 @@ import ( "context" "net/http" - "github.com/jiaozifs/jiaozifs/models/rbacmodel" + "github.com/GitDataAI/jiaozifs/models/rbacmodel" - "github.com/jiaozifs/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/utils" - "github.com/jiaozifs/jiaozifs/auth" + "github.com/GitDataAI/jiaozifs/auth" - "github.com/jiaozifs/jiaozifs/auth/rbac" + "github.com/GitDataAI/jiaozifs/auth/rbac" - "github.com/jiaozifs/jiaozifs/api" - "github.com/jiaozifs/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/api" + "github.com/GitDataAI/jiaozifs/models" "go.uber.org/fx" ) diff --git a/controller/helper.go b/controller/helper.go index 2a4cbc89..541550f3 100644 --- a/controller/helper.go +++ b/controller/helper.go @@ -3,9 +3,9 @@ package controller import ( "encoding/hex" - "github.com/jiaozifs/jiaozifs/api" - "github.com/jiaozifs/jiaozifs/utils" - "github.com/jiaozifs/jiaozifs/versionmgr" + "github.com/GitDataAI/jiaozifs/api" + "github.com/GitDataAI/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/versionmgr" ) func changesToDTO(changes *versionmgr.Changes) ([]api.Change, error) { diff --git a/controller/member_ctl.go b/controller/member_ctl.go index 2cd83bd9..f3d94232 100644 --- a/controller/member_ctl.go +++ b/controller/member_ctl.go @@ -5,12 +5,12 @@ import ( "net/http" "time" - "github.com/jiaozifs/jiaozifs/models/rbacmodel" - "github.com/jiaozifs/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/models/rbacmodel" + "github.com/GitDataAI/jiaozifs/utils" - "github.com/jiaozifs/jiaozifs/api" - "github.com/jiaozifs/jiaozifs/auth/rbac" - "github.com/jiaozifs/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/api" + "github.com/GitDataAI/jiaozifs/auth/rbac" + "github.com/GitDataAI/jiaozifs/models" "go.uber.org/fx" ) diff --git a/controller/merge_request_ctl.go b/controller/merge_request_ctl.go index b3982210..d6e6d4b2 100644 --- a/controller/merge_request_ctl.go +++ b/controller/merge_request_ctl.go @@ -7,18 +7,18 @@ import ( "net/http" "time" - "github.com/jiaozifs/jiaozifs/auth/rbac" - "github.com/jiaozifs/jiaozifs/models/rbacmodel" - "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/GitDataAI/jiaozifs/auth/rbac" + "github.com/GitDataAI/jiaozifs/models/rbacmodel" + "github.com/GitDataAI/jiaozifs/utils/hash" - "github.com/jiaozifs/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/utils" - "github.com/jiaozifs/jiaozifs/versionmgr" + "github.com/GitDataAI/jiaozifs/versionmgr" - "github.com/jiaozifs/jiaozifs/api" - "github.com/jiaozifs/jiaozifs/auth" - "github.com/jiaozifs/jiaozifs/block/params" - "github.com/jiaozifs/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/api" + "github.com/GitDataAI/jiaozifs/auth" + "github.com/GitDataAI/jiaozifs/block/params" + "github.com/GitDataAI/jiaozifs/models" "go.uber.org/fx" ) @@ -381,17 +381,17 @@ func (mrCtl MergeRequestController) Merge(ctx context.Context, w *api.JiaozifsRe var commit *models.Commit err = mrCtl.Repo.Transaction(ctx, func(repo models.IRepo) error { - workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repository, mrCtl.Repo, mrCtl.PublicStorageConfig) + workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repository, repo, mrCtl.PublicStorageConfig) if err != nil { return err } - sourceBranch, err := mrCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetID(mergeRequest.SourceBranchID)) + sourceBranch, err := repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetID(mergeRequest.SourceBranchID)) if err != nil { return err } - targetBranch, err := mrCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetID(mergeRequest.TargetBranchID)) + targetBranch, err := repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetID(mergeRequest.TargetBranchID)) if err != nil { return err } @@ -406,7 +406,7 @@ func (mrCtl MergeRequestController) Merge(ctx context.Context, w *api.JiaozifsRe return err } - return mrCtl.Repo.MergeRequestRepo().UpdateByID(ctx, models.NewUpdateMergeRequestParams(repository.ID, mergeRequest.Sequence).SetState(models.MergeStateMerged)) + return repo.MergeRequestRepo().UpdateByID(ctx, models.NewUpdateMergeRequestParams(repository.ID, mergeRequest.Sequence).SetState(models.MergeStateMerged)) }) if err != nil { w.Error(err) diff --git a/controller/object_ctl.go b/controller/object_ctl.go index 96513e68..a6574e90 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -10,21 +10,21 @@ import ( "net/http" "time" - "github.com/jiaozifs/jiaozifs/auth/rbac" - "github.com/jiaozifs/jiaozifs/controller/validator" - + "github.com/GitDataAI/jiaozifs/auth/rbac" + "github.com/GitDataAI/jiaozifs/controller/validator" + + "github.com/GitDataAI/jiaozifs/api" + "github.com/GitDataAI/jiaozifs/auth" + "github.com/GitDataAI/jiaozifs/block/params" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/models/filemode" + "github.com/GitDataAI/jiaozifs/models/rbacmodel" + "github.com/GitDataAI/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/utils/hash" + "github.com/GitDataAI/jiaozifs/utils/httputil" + "github.com/GitDataAI/jiaozifs/versionmgr" "github.com/go-openapi/swag" logging "github.com/ipfs/go-log/v2" - "github.com/jiaozifs/jiaozifs/api" - "github.com/jiaozifs/jiaozifs/auth" - "github.com/jiaozifs/jiaozifs/block/params" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/models/filemode" - "github.com/jiaozifs/jiaozifs/models/rbacmodel" - "github.com/jiaozifs/jiaozifs/utils" - "github.com/jiaozifs/jiaozifs/utils/hash" - "github.com/jiaozifs/jiaozifs/utils/httputil" - "github.com/jiaozifs/jiaozifs/versionmgr" "go.uber.org/fx" ) diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index 536c8b46..70885f4a 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -8,21 +8,21 @@ import ( "net/http" "time" - "github.com/jiaozifs/jiaozifs/auth/rbac" - "github.com/jiaozifs/jiaozifs/controller/validator" - "github.com/jiaozifs/jiaozifs/models/rbacmodel" - + "github.com/GitDataAI/jiaozifs/auth/rbac" + "github.com/GitDataAI/jiaozifs/controller/validator" + "github.com/GitDataAI/jiaozifs/models/rbacmodel" + + "github.com/GitDataAI/jiaozifs/api" + "github.com/GitDataAI/jiaozifs/auth" + "github.com/GitDataAI/jiaozifs/block/factory" + "github.com/GitDataAI/jiaozifs/block/params" + "github.com/GitDataAI/jiaozifs/config" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/utils/hash" + "github.com/GitDataAI/jiaozifs/versionmgr" "github.com/google/uuid" logging "github.com/ipfs/go-log/v2" - "github.com/jiaozifs/jiaozifs/api" - "github.com/jiaozifs/jiaozifs/auth" - "github.com/jiaozifs/jiaozifs/block/factory" - "github.com/jiaozifs/jiaozifs/block/params" - "github.com/jiaozifs/jiaozifs/config" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/utils" - "github.com/jiaozifs/jiaozifs/utils/hash" - "github.com/jiaozifs/jiaozifs/versionmgr" "go.uber.org/fx" ) @@ -252,11 +252,11 @@ func (repositoryCtl RepositoryController) CreateRepository(ctx context.Context, //create default ref var createdRepo *models.Repository err = repositoryCtl.Repo.Transaction(ctx, func(repo models.IRepo) error { - _, err := repositoryCtl.Repo.BranchRepo().Insert(ctx, defaultRef) + _, err := repo.BranchRepo().Insert(ctx, defaultRef) if err != nil { return err } - createdRepo, err = repositoryCtl.Repo.RepositoryRepo().Insert(ctx, repository) + createdRepo, err = repo.RepositoryRepo().Insert(ctx, repository) return err }) if err != nil { @@ -291,7 +291,7 @@ func (repositoryCtl RepositoryController) DeleteRepository(ctx context.Context, err = repositoryCtl.Repo.Transaction(ctx, func(repo models.IRepo) error { // delete repository - affectRows, err := repositoryCtl.Repo.RepositoryRepo().Delete(ctx, models.NewDeleteRepoParams().SetID(repository.ID)) + affectRows, err := repo.RepositoryRepo().Delete(ctx, models.NewDeleteRepoParams().SetID(repository.ID)) if err != nil { return err } @@ -301,37 +301,37 @@ func (repositoryCtl RepositoryController) DeleteRepository(ctx context.Context, } //delete branch - _, err = repositoryCtl.Repo.BranchRepo().Delete(ctx, models.NewDeleteBranchParams().SetRepositoryID(repository.ID)) + _, err = repo.BranchRepo().Delete(ctx, models.NewDeleteBranchParams().SetRepositoryID(repository.ID)) if err != nil { return err } //delete commit - _, err = repositoryCtl.Repo.CommitRepo(repository.ID).Delete(ctx, models.NewDeleteParams()) + _, err = repo.CommitRepo(repository.ID).Delete(ctx, models.NewDeleteParams()) if err != nil { return err } //delete tag - _, err = repositoryCtl.Repo.TagRepo(repository.ID).Delete(ctx, models.NewDeleteParams()) + _, err = repo.TagRepo(repository.ID).Delete(ctx, models.NewDeleteParams()) if err != nil { return err } // delete tree - _, err = repositoryCtl.Repo.FileTreeRepo(repository.ID).Delete(ctx, models.NewDeleteTreeParams()) + _, err = repo.FileTreeRepo(repository.ID).Delete(ctx, models.NewDeleteTreeParams()) if err != nil { return err } //delete wip - _, err = repositoryCtl.Repo.WipRepo().Delete(ctx, models.NewDeleteWipParams().SetRepositoryID(repository.ID)) + _, err = repo.WipRepo().Delete(ctx, models.NewDeleteWipParams().SetRepositoryID(repository.ID)) if err != nil { return err } //delete all membership - _, err = repositoryCtl.Repo.MemberRepo().DeleteMember(ctx, models.NewDeleteMemberParams().SetRepoID(repository.ID)) + _, err = repo.MemberRepo().DeleteMember(ctx, models.NewDeleteMemberParams().SetRepoID(repository.ID)) return err }) if err != nil { diff --git a/controller/user_ctl.go b/controller/user_ctl.go index 9859538e..6fd61f38 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -7,18 +7,18 @@ import ( "net/http" "time" - "github.com/jiaozifs/jiaozifs/auth/rbac" - "github.com/jiaozifs/jiaozifs/controller/validator" - "github.com/jiaozifs/jiaozifs/models/rbacmodel" - "github.com/jiaozifs/jiaozifs/utils" - + "github.com/GitDataAI/jiaozifs/auth/rbac" + "github.com/GitDataAI/jiaozifs/controller/validator" + "github.com/GitDataAI/jiaozifs/models/rbacmodel" + "github.com/GitDataAI/jiaozifs/utils" + + "github.com/GitDataAI/jiaozifs/api" + "github.com/GitDataAI/jiaozifs/auth" + "github.com/GitDataAI/jiaozifs/config" + "github.com/GitDataAI/jiaozifs/models" "github.com/go-openapi/swag" "github.com/gorilla/sessions" logging "github.com/ipfs/go-log/v2" - "github.com/jiaozifs/jiaozifs/api" - "github.com/jiaozifs/jiaozifs/auth" - "github.com/jiaozifs/jiaozifs/config" - "github.com/jiaozifs/jiaozifs/models" openapitypes "github.com/oapi-codegen/runtime/types" "go.uber.org/fx" ) diff --git a/controller/wip_ctl.go b/controller/wip_ctl.go index 375cfac6..a9f8fa77 100644 --- a/controller/wip_ctl.go +++ b/controller/wip_ctl.go @@ -6,15 +6,15 @@ import ( "fmt" "net/http" - "github.com/jiaozifs/jiaozifs/api" - "github.com/jiaozifs/jiaozifs/auth" - "github.com/jiaozifs/jiaozifs/auth/rbac" - "github.com/jiaozifs/jiaozifs/block/params" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/models/rbacmodel" - "github.com/jiaozifs/jiaozifs/utils" - "github.com/jiaozifs/jiaozifs/utils/hash" - "github.com/jiaozifs/jiaozifs/versionmgr" + "github.com/GitDataAI/jiaozifs/api" + "github.com/GitDataAI/jiaozifs/auth" + "github.com/GitDataAI/jiaozifs/auth/rbac" + "github.com/GitDataAI/jiaozifs/block/params" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/models/rbacmodel" + "github.com/GitDataAI/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/utils/hash" + "github.com/GitDataAI/jiaozifs/versionmgr" "go.uber.org/fx" ) diff --git a/docs/rbac.md b/docs/rbac.md index c99e01dd..f78b2d0c 100644 --- a/docs/rbac.md +++ b/docs/rbac.md @@ -2,7 +2,7 @@ rbac中引入resource_type, resource, statement, policy, group,user,的概念他们之间的关系如下图 -![image](https://github.com/jiaozifs/jiaozifs/assets/41407352/632d8b90-25d4-423e-bcea-5114c339ddf8) +![image](https://github.com/GitDataAI/jiaozifs/assets/41407352/632d8b90-25d4-423e-bcea-5114c339ddf8) ## 类型组织 整体上对于权限的组织情况如下 diff --git a/examples/aksk.go b/examples/aksk.go index fa7b9d9c..baaf9643 100644 --- a/examples/aksk.go +++ b/examples/aksk.go @@ -6,10 +6,10 @@ import ( "flag" "log" - "github.com/jiaozifs/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/utils" - "github.com/jiaozifs/jiaozifs/api" - apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" + "github.com/GitDataAI/jiaozifs/api" + apiimpl "github.com/GitDataAI/jiaozifs/api/api_impl" ) var url string diff --git a/fx_opt/options.go b/fx_opt/options.go index 17d34bba..53a18c26 100644 --- a/fx_opt/options.go +++ b/fx_opt/options.go @@ -113,7 +113,7 @@ func ApplyIf(check func(s *Settings) bool, opts ...Option) Option { } func If(b bool, opts ...Option) Option { - return ApplyIf(func(s *Settings) bool { + return ApplyIf(func(_ *Settings) bool { return b }, opts...) } @@ -192,7 +192,7 @@ func as(in interface{}, as interface{}) interface{} { if inType.Kind() != reflect.Func || inType.AssignableTo(outType.Elem()) { ctype := reflect.FuncOf(nil, []reflect.Type{outType.Elem()}, false) - return reflect.MakeFunc(ctype, func(args []reflect.Value) (results []reflect.Value) { + return reflect.MakeFunc(ctype, func(_ []reflect.Value) (results []reflect.Value) { out := reflect.New(outType.Elem()) out.Elem().Set(reflect.ValueOf(in)) diff --git a/go.mod b/go.mod index d1d5b8a0..be2f406e 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/jiaozifs/jiaozifs +module github.com/GitDataAI/jiaozifs go 1.21 @@ -39,6 +39,7 @@ require ( github.com/ipfs/boxo v0.18.0 github.com/ipfs/go-log/v2 v2.5.1 github.com/ipfs/kubo v0.26.0 + github.com/m1/go-generate-password v0.2.0 github.com/matoous/go-nanoid/v2 v2.0.0 github.com/minio/minio-go/v7 v7.0.64 github.com/mitchellh/go-homedir v1.1.0 @@ -177,7 +178,6 @@ require ( github.com/libp2p/go-libp2p-routing-helpers v0.7.3 // indirect github.com/libp2p/go-msgio v0.3.0 // indirect github.com/libp2p/go-netroute v0.2.1 // indirect - github.com/m1/go-generate-password v0.2.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect diff --git a/integrationtest/aksk_test.go b/integrationtest/aksk_test.go index 1e9de7f8..0cfdf8fe 100644 --- a/integrationtest/aksk_test.go +++ b/integrationtest/aksk_test.go @@ -4,10 +4,10 @@ import ( "context" "net/http" - "github.com/jiaozifs/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/utils" - "github.com/jiaozifs/jiaozifs/api" - apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" + "github.com/GitDataAI/jiaozifs/api" + apiimpl "github.com/GitDataAI/jiaozifs/api/api_impl" "github.com/smartystreets/goconvey/convey" ) @@ -17,7 +17,7 @@ func AkSkSpec(ctx context.Context, urlStr string) func(c convey.C) { return func(c convey.C) { userName := "muly" - c.Convey("init", func(c convey.C) { + c.Convey("init", func(_ convey.C) { createUser(ctx, client, userName) loginAndSwitch(ctx, client, userName, false) }) @@ -142,7 +142,7 @@ func AkSkSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("aksk user", func(c convey.C) { - c.Convey("success", func(c convey.C) { + c.Convey("success", func(_ convey.C) { aksk := createAksk(ctx, client) cli, err := api.NewClient(urlStr+apiimpl.APIV1Prefix, api.AkSkOption(aksk.AccessKey, aksk.SecretKey)) @@ -155,7 +155,7 @@ func AkSkSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(err, convey.ShouldBeNil) convey.ShouldEqual(user.JSON200.Name, userName) }) - c.Convey("wrong sk", func(c convey.C) { + c.Convey("wrong sk", func(_ convey.C) { aksk := createAksk(ctx, client) client, err := api.NewClient(urlStr+apiimpl.APIV1Prefix, api.AkSkOption(aksk.AccessKey, "fakesk")) @@ -166,7 +166,7 @@ func AkSkSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.ShouldEqual(resp.StatusCode, http.StatusUnauthorized) }) - c.Convey("ak not exit", func(c convey.C) { + c.Convey("ak not exit", func(_ convey.C) { client, err := api.NewClient(urlStr+apiimpl.APIV1Prefix, api.AkSkOption("fakesk", "fakesk")) convey.So(err, convey.ShouldBeNil) diff --git a/integrationtest/branch_test.go b/integrationtest/branch_test.go index 3d985800..4b7df54f 100644 --- a/integrationtest/branch_test.go +++ b/integrationtest/branch_test.go @@ -5,11 +5,11 @@ import ( "net/http" "strings" - "github.com/jiaozifs/jiaozifs/controller/validator" + "github.com/GitDataAI/jiaozifs/controller/validator" - "github.com/jiaozifs/jiaozifs/api" - apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" - "github.com/jiaozifs/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/api" + apiimpl "github.com/GitDataAI/jiaozifs/api/api_impl" + "github.com/GitDataAI/jiaozifs/utils" "github.com/smartystreets/goconvey/convey" ) @@ -23,7 +23,7 @@ func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { newAmount := 0 prefix := "feat/" - c.Convey("init", func(c convey.C) { + c.Convey("init", func(_ convey.C) { _ = createUser(ctx, client, userName) loginAndSwitch(ctx, client, userName, false) _ = createRepo(ctx, client, repoName, false) diff --git a/integrationtest/commit_test.go b/integrationtest/commit_test.go index e3e8a706..7579e348 100644 --- a/integrationtest/commit_test.go +++ b/integrationtest/commit_test.go @@ -4,11 +4,11 @@ import ( "context" "net/http" - "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/GitDataAI/jiaozifs/utils/hash" - "github.com/jiaozifs/jiaozifs/api" - apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" - "github.com/jiaozifs/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/api" + apiimpl "github.com/GitDataAI/jiaozifs/api/api_impl" + "github.com/GitDataAI/jiaozifs/utils" "github.com/smartystreets/goconvey/convey" ) @@ -20,7 +20,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { repoName := "black" branchName := "feat/get_entries_test" - c.Convey("init", func(c convey.C) { + c.Convey("init", func(_ convey.C) { _ = createUser(ctx, client, userName) loginAndSwitch(ctx, client, userName, false) _ = createRepo(ctx, client, repoName, false) @@ -127,7 +127,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { }) }) - c.Convey("commit kitty first changes", func(c convey.C) { + c.Convey("commit kitty first changes", func(_ convey.C) { commitWip(ctx, client, userName, repoName, branchName, "test") }) @@ -228,7 +228,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { }) }) - c.Convey("prepare data for commit test", func(c convey.C) { + c.Convey("prepare data for commit test", func(_ convey.C) { createWip(ctx, client, userName, repoName, "main") uploadObject(ctx, client, userName, repoName, "main", "a.dat") //delete\ uploadObject(ctx, client, userName, repoName, "main", "g/m.dat") //modify @@ -384,7 +384,7 @@ func GetCommitChangesSpec(ctx context.Context, urlStr string) func(c convey.C) { userName := "kelly" repoName := "gcc" - c.Convey("init", func(c convey.C) { + c.Convey("init", func(_ convey.C) { createUser(ctx, client, userName) loginAndSwitch(ctx, client, userName, false) createRepo(ctx, client, repoName, false) diff --git a/integrationtest/group_test.go b/integrationtest/group_test.go index ec0c9168..4b5c15f7 100644 --- a/integrationtest/group_test.go +++ b/integrationtest/group_test.go @@ -3,8 +3,8 @@ package integrationtest import ( "context" - "github.com/jiaozifs/jiaozifs/api" - apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" + "github.com/GitDataAI/jiaozifs/api" + apiimpl "github.com/GitDataAI/jiaozifs/api/api_impl" "github.com/smartystreets/goconvey/convey" ) diff --git a/integrationtest/helper_test.go b/integrationtest/helper_test.go index b91e9686..100269e4 100644 --- a/integrationtest/helper_test.go +++ b/integrationtest/helper_test.go @@ -16,10 +16,10 @@ import ( openapi_types "github.com/oapi-codegen/runtime/types" - "github.com/jiaozifs/jiaozifs/api" - "github.com/jiaozifs/jiaozifs/cmd" - "github.com/jiaozifs/jiaozifs/testhelper" - "github.com/jiaozifs/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/api" + "github.com/GitDataAI/jiaozifs/cmd" + "github.com/GitDataAI/jiaozifs/testhelper" + "github.com/GitDataAI/jiaozifs/utils" "github.com/phayes/freeport" "github.com/smartystreets/goconvey/convey" "github.com/stretchr/testify/require" @@ -133,7 +133,7 @@ func loginAndSwitch(ctx context.Context, client *api.Client, userName string, us convey.So(err, convey.ShouldBeNil) client.RequestEditors = nil - client.RequestEditors = append(client.RequestEditors, func(ctx context.Context, req *http.Request) error { + client.RequestEditors = append(client.RequestEditors, func(_ context.Context, req *http.Request) error { if useCookie { for _, cookie := range resp.Cookies() { req.AddCookie(cookie) diff --git a/integrationtest/member_test.go b/integrationtest/member_test.go index 16211f22..b1529423 100644 --- a/integrationtest/member_test.go +++ b/integrationtest/member_test.go @@ -4,10 +4,10 @@ import ( "context" "net/http" - "github.com/jiaozifs/jiaozifs/auth/rbac" + "github.com/GitDataAI/jiaozifs/auth/rbac" - "github.com/jiaozifs/jiaozifs/api" - apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" + "github.com/GitDataAI/jiaozifs/api" + apiimpl "github.com/GitDataAI/jiaozifs/api/api_impl" "github.com/smartystreets/goconvey/convey" ) @@ -348,7 +348,7 @@ func getToken(ctx context.Context, client *api.Client, userName string) []api.Re loginResult, err := api.ParseLoginResponse(resp) convey.So(err, convey.ShouldBeNil) - return []api.RequestEditorFn{func(ctx context.Context, req *http.Request) error { + return []api.RequestEditorFn{func(_ context.Context, req *http.Request) error { req.Header.Add("Authorization", "Bearer "+loginResult.JSON200.Token) return nil }} diff --git a/integrationtest/merge_request_test.go b/integrationtest/merge_request_test.go index 195190ba..5023a7ee 100644 --- a/integrationtest/merge_request_test.go +++ b/integrationtest/merge_request_test.go @@ -7,12 +7,12 @@ import ( "strconv" "time" - "github.com/jiaozifs/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/utils" - "github.com/jiaozifs/jiaozifs/api" - apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" + "github.com/GitDataAI/jiaozifs/api" + apiimpl "github.com/GitDataAI/jiaozifs/api/api_impl" "github.com/smartystreets/goconvey/convey" ) @@ -24,7 +24,7 @@ func MergeRequestSpec(ctx context.Context, urlStr string) func(c convey.C) { repoName := "mr_test" branchName := "feat/obj_test" - c.Convey("init", func(c convey.C) { + c.Convey("init", func(_ convey.C) { _ = createUser(ctx, client, userName) loginAndSwitch(ctx, client, userName, false) _ = createRepo(ctx, client, repoName, false) @@ -254,7 +254,7 @@ func MergeRequestSpec(ctx context.Context, urlStr string) func(c convey.C) { }) }) - c.Convey("create many mergequests", func(c convey.C) { + c.Convey("create many mergequests", func(_ convey.C) { for i := 0; i < 10; i++ { branchName := fmt.Sprintf("feat/list_merge_test_%d", i) createBranch(ctx, client, userName, repoName, "main", branchName) diff --git a/integrationtest/objects_test.go b/integrationtest/objects_test.go index 49a05341..2dac5e0b 100644 --- a/integrationtest/objects_test.go +++ b/integrationtest/objects_test.go @@ -8,9 +8,9 @@ import ( "io" "net/http" - "github.com/jiaozifs/jiaozifs/api" - apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" - "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/GitDataAI/jiaozifs/api" + apiimpl "github.com/GitDataAI/jiaozifs/api/api_impl" + "github.com/GitDataAI/jiaozifs/utils/hash" "github.com/smartystreets/goconvey/convey" ) @@ -21,7 +21,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { repoName := "dataspace" branchName := "feat/obj_test" - c.Convey("init", func(c convey.C) { + c.Convey("init", func(_ convey.C) { _ = createUser(ctx, client, userName) loginAndSwitch(ctx, client, userName, false) _ = createRepo(ctx, client, repoName, false) @@ -123,7 +123,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { }) //commit object to branch - c.Convey("commit object to branch", func(c convey.C) { + c.Convey("commit object to branch", func(_ convey.C) { commitWip(ctx, client, userName, repoName, branchName, "test commit msg") }) diff --git a/integrationtest/public_repo_test.go b/integrationtest/public_repo_test.go index 90cf78d5..11a96dff 100644 --- a/integrationtest/public_repo_test.go +++ b/integrationtest/public_repo_test.go @@ -6,10 +6,10 @@ import ( "net/http" "strconv" - "github.com/jiaozifs/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/utils" - "github.com/jiaozifs/jiaozifs/api" - apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" + "github.com/GitDataAI/jiaozifs/api" + apiimpl "github.com/GitDataAI/jiaozifs/api/api_impl" "github.com/smartystreets/goconvey/convey" ) @@ -24,7 +24,7 @@ func PublicRepoSpec(ctx context.Context, urlStr string) func(c convey.C) { var user1Token, user2Token []api.RequestEditorFn return func(c convey.C) { - c.Convey("init", func(c convey.C) { + c.Convey("init", func(_ convey.C) { _ = createUser(ctx, client, user1Name) _ = createUser(ctx, client, user2Name) user1Token = getToken(ctx, client, user1Name) diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index 283a6b58..adbf8e9b 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -4,10 +4,10 @@ import ( "context" "net/http" - "github.com/jiaozifs/jiaozifs/api" - apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" - "github.com/jiaozifs/jiaozifs/controller" - "github.com/jiaozifs/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/api" + apiimpl "github.com/GitDataAI/jiaozifs/api/api_impl" + "github.com/GitDataAI/jiaozifs/controller" + "github.com/GitDataAI/jiaozifs/utils" "github.com/smartystreets/goconvey/convey" ) @@ -17,7 +17,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { userName := "jimmy" repoName := "happyrun" - c.Convey("init", func(c convey.C) { + c.Convey("init", func(_ convey.C) { loginAndSwitch(ctx, client, "admin2", true) _ = createRepo(ctx, client, "admin2_repo", false) @@ -354,7 +354,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) }) - c.Convey("create branch", func(c convey.C) { + c.Convey("create branch", func(_ convey.C) { createBranch(ctx, client, userName, repoName, "main", "feat/ano_branch") }) @@ -403,7 +403,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { }) - c.Convey("add commit to branch", func(c convey.C) { + c.Convey("add commit to branch", func(_ convey.C) { createWip(ctx, client, userName, repoName, controller.DefaultBranchName) uploadObject(ctx, client, userName, repoName, controller.DefaultBranchName, "a.txt") commitWip(ctx, client, userName, repoName, controller.DefaultBranchName, "first commit") @@ -422,7 +422,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So((*result.JSON200)[0].Message, convey.ShouldEqual, "first commit") }) - c.Convey("add double commit to branch", func(c convey.C) { + c.Convey("add double commit to branch", func(_ convey.C) { uploadObject(ctx, client, userName, repoName, controller.DefaultBranchName, "b.txt") commitWip(ctx, client, userName, repoName, controller.DefaultBranchName, "second commit") uploadObject(ctx, client, userName, repoName, controller.DefaultBranchName, "c.txt") diff --git a/integrationtest/status_test.go b/integrationtest/status_test.go index 2f3b18f3..c3a7c814 100644 --- a/integrationtest/status_test.go +++ b/integrationtest/status_test.go @@ -9,7 +9,7 @@ import ( ) func StatusSpec(_ context.Context, urlStr string) func(c convey.C) { - return func(c convey.C) { + return func(_ convey.C) { url, err := url.Parse(urlStr) convey.ShouldBeNil(err) url.Path = "/status" diff --git a/integrationtest/user_test.go b/integrationtest/user_test.go index 1fd03bf1..129da3d3 100644 --- a/integrationtest/user_test.go +++ b/integrationtest/user_test.go @@ -6,8 +6,8 @@ import ( openapi_types "github.com/oapi-codegen/runtime/types" - "github.com/jiaozifs/jiaozifs/api" - apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" + "github.com/GitDataAI/jiaozifs/api" + apiimpl "github.com/GitDataAI/jiaozifs/api/api_impl" "github.com/smartystreets/goconvey/convey" ) @@ -16,7 +16,7 @@ func UserSpec(ctx context.Context, urlStr string) func(c convey.C) { return func(c convey.C) { userName := "admin2" - c.Convey("init user", func(c convey.C) { + c.Convey("init user", func(_ convey.C) { _ = createUser(ctx, client, userName) }) @@ -45,7 +45,7 @@ func UserSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) }) - c.Convey("admin login", func(c convey.C) { + c.Convey("admin login", func(_ convey.C) { loginAndSwitch(ctx, client, userName, false) }) @@ -55,7 +55,7 @@ func UserSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) }) - c.Convey("admin login again", func(c convey.C) { + c.Convey("admin login again", func(_ convey.C) { loginAndSwitch(ctx, client, userName, true) }) diff --git a/integrationtest/wip_object_test.go b/integrationtest/wip_object_test.go index 472b9761..ecae89d7 100644 --- a/integrationtest/wip_object_test.go +++ b/integrationtest/wip_object_test.go @@ -4,11 +4,11 @@ import ( "context" "net/http" - "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/GitDataAI/jiaozifs/utils/hash" - "github.com/jiaozifs/jiaozifs/api" - apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" - "github.com/jiaozifs/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/api" + apiimpl "github.com/GitDataAI/jiaozifs/api/api_impl" + "github.com/GitDataAI/jiaozifs/utils" "github.com/smartystreets/goconvey/convey" ) @@ -19,7 +19,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { repoName := "hash" branchName := "feat/wip_obj_test" - c.Convey("init", func(c convey.C) { + c.Convey("init", func(_ convey.C) { _ = createUser(ctx, client, userName) loginAndSwitch(ctx, client, userName, false) _ = createRepo(ctx, client, repoName, false) @@ -297,7 +297,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { }) testBranchName := "test/empty_branch" - c.Convey("update objects and create empty branch", func(c convey.C) { + c.Convey("update objects and create empty branch", func(_ convey.C) { uploadObject(ctx, client, userName, repoName, branchName, "a/m.dat") uploadObject(ctx, client, userName, repoName, branchName, "a/b.dat") uploadObject(ctx, client, userName, repoName, branchName, "b.dat") @@ -307,7 +307,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { createWip(ctx, client, userName, repoName, testBranchName) }) - c.Convey("get wip success on init", func(c convey.C) { + c.Convey("get wip success on init", func(_ convey.C) { resp, err := client.GetWipChanges(ctx, userName, repoName, &api.GetWipChangesParams{ RefName: testBranchName, }) @@ -474,7 +474,7 @@ func UpdateWipSpec(ctx context.Context, urlStr string) func(c convey.C) { repoName := "update_wip_test" branchName := "main" - c.Convey("create wip", func(c convey.C) { + c.Convey("create wip", func(_ convey.C) { _ = createUser(ctx, client, userName) loginAndSwitch(ctx, client, userName, false) _ = createRepo(ctx, client, repoName, false) @@ -488,7 +488,7 @@ func UpdateWipSpec(ctx context.Context, urlStr string) func(c convey.C) { _ = uploadObject(ctx, client, userName, repoName, branchName, "g/m.dat") }) - c.Convey("get wip", func(c convey.C) { + c.Convey("get wip", func(_ convey.C) { resp, err := client.GetWip(ctx, userName, repoName, &api.GetWipParams{ RefName: branchName, }) diff --git a/integrationtest/wip_test.go b/integrationtest/wip_test.go index 264ce4a8..ab644247 100644 --- a/integrationtest/wip_test.go +++ b/integrationtest/wip_test.go @@ -4,8 +4,8 @@ import ( "context" "net/http" - "github.com/jiaozifs/jiaozifs/api" - apiimpl "github.com/jiaozifs/jiaozifs/api/api_impl" + "github.com/GitDataAI/jiaozifs/api" + apiimpl "github.com/GitDataAI/jiaozifs/api/api_impl" "github.com/smartystreets/goconvey/convey" ) @@ -17,7 +17,7 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { branchName := "feat/wip_test" branchNameForDelete := "feat/wip_test2" - c.Convey("init", func(c convey.C) { + c.Convey("init", func(_ convey.C) { _ = createUser(ctx, client, userName) loginAndSwitch(ctx, client, userName, false) _ = createRepo(ctx, client, repoName, false) @@ -25,7 +25,7 @@ func WipSpec(ctx context.Context, urlStr string) func(c convey.C) { _ = createBranch(ctx, client, userName, repoName, "main", branchNameForDelete) }) - c.Convey("list non exit wip", func(c convey.C) { + c.Convey("list non exit wip", func(_ convey.C) { resp, err := client.ListWip(ctx, userName, repoName) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) diff --git a/main.go b/main.go index 2d0a5ddf..ee75f35b 100644 --- a/main.go +++ b/main.go @@ -1,11 +1,11 @@ /* -Copyright © 2023 githun.com/jiaozifs/jiaozifs +Copyright © 2023 githun.com/GitDataAI/jiaozifs */ package main import ( + "github.com/GitDataAI/jiaozifs/cmd" _ "github.com/deepmap/oapi-codegen/v2/pkg/codegen" - "github.com/jiaozifs/jiaozifs/cmd" _ "gopkg.in/yaml.v2" ) diff --git a/makefile b/makefile index 33487110..18455200 100644 --- a/makefile +++ b/makefile @@ -6,7 +6,7 @@ GOGENERATE=$(GOCMD) generate all: build .PHONY: all -ldflags=-X=github.com/jiaozifs/jiaozifs/version.CurrentCommit=+git.$(subst -,.,$(shell git describe --always --match=NeVeRmAtCh --dirty 2>/dev/null || git rev-parse --short HEAD 2>/dev/null)) +ldflags=-X=github.com/GitDataAI/jiaozifs/version.CurrentCommit=+git.$(subst -,.,$(shell git describe --always --match=NeVeRmAtCh --dirty 2>/dev/null || git rev-parse --short HEAD 2>/dev/null)) ifneq ($(strip $(LDFLAGS)),) ldflags+=-extldflags=$(LDFLAGS) endif diff --git a/models/aksk_test.go b/models/aksk_test.go index 1764c192..c80d2ed6 100644 --- a/models/aksk_test.go +++ b/models/aksk_test.go @@ -6,10 +6,10 @@ import ( "github.com/google/uuid" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/testhelper" "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/testhelper" "github.com/stretchr/testify/require" ) diff --git a/models/branch.go b/models/branch.go index bb4580e7..ff14d271 100644 --- a/models/branch.go +++ b/models/branch.go @@ -4,8 +4,8 @@ import ( "context" "time" + "github.com/GitDataAI/jiaozifs/utils/hash" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/uptrace/bun" ) diff --git a/models/branch_test.go b/models/branch_test.go index 6d44b6a4..bb210f85 100644 --- a/models/branch_test.go +++ b/models/branch_test.go @@ -4,12 +4,12 @@ import ( "context" "testing" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/testhelper" + "github.com/GitDataAI/jiaozifs/utils/hash" "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/testhelper" - "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/stretchr/testify/require" ) diff --git a/models/commit.go b/models/commit.go index 8f1c6b08..1783d4e6 100644 --- a/models/commit.go +++ b/models/commit.go @@ -4,8 +4,8 @@ import ( "context" "time" + "github.com/GitDataAI/jiaozifs/utils/hash" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/uptrace/bun" ) diff --git a/models/commit_test.go b/models/commit_test.go index ff767762..d4959530 100644 --- a/models/commit_test.go +++ b/models/commit_test.go @@ -4,11 +4,11 @@ import ( "context" "testing" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/testhelper" "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/testhelper" "github.com/stretchr/testify/require" ) diff --git a/models/members_test.go b/models/members_test.go index f7a84297..e61af1cb 100644 --- a/models/members_test.go +++ b/models/members_test.go @@ -5,15 +5,15 @@ import ( "testing" "time" - "github.com/jiaozifs/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/utils" "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" "github.com/google/uuid" "github.com/stretchr/testify/require" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/testhelper" ) func TestMemberRepo(t *testing.T) { diff --git a/models/merge_request_test.go b/models/merge_request_test.go index b773fe1d..f2ac8b0e 100644 --- a/models/merge_request_test.go +++ b/models/merge_request_test.go @@ -5,13 +5,12 @@ import ( "testing" "time" - "github.com/jiaozifs/jiaozifs/utils" - + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/testhelper" + "github.com/GitDataAI/jiaozifs/utils" "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/testhelper" "github.com/stretchr/testify/require" ) diff --git a/models/migrations/20210505110026_init_project.go b/models/migrations/20210505110026_init_project.go index b088b3bb..5e72aae2 100644 --- a/models/migrations/20210505110026_init_project.go +++ b/models/migrations/20210505110026_init_project.go @@ -3,8 +3,8 @@ package migrations import ( "context" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/models/rbacmodel" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/models/rbacmodel" "github.com/uptrace/bun" ) diff --git a/models/models.go b/models/models.go index f2452fc6..e30d518a 100644 --- a/models/models.go +++ b/models/models.go @@ -4,7 +4,7 @@ import ( "context" "database/sql" - "github.com/jiaozifs/jiaozifs/config" + "github.com/GitDataAI/jiaozifs/config" "github.com/uptrace/bun" "github.com/uptrace/bun/dialect/pgdialect" "github.com/uptrace/bun/driver/pgdriver" @@ -19,7 +19,7 @@ func SetupDatabase(ctx context.Context, lc fx.Lifecycle, dbConfig *config.Databa } lc.Append(fx.Hook{ - OnStop: func(ctx context.Context) error { + OnStop: func(_ context.Context) error { return bunDB.Close() }, }) diff --git a/models/models_test.go b/models/models_test.go index 0a3313f4..1086935f 100644 --- a/models/models_test.go +++ b/models/models_test.go @@ -5,9 +5,9 @@ import ( "fmt" "testing" + "github.com/GitDataAI/jiaozifs/config" + "github.com/GitDataAI/jiaozifs/models" embeddedpostgres "github.com/fergusstrange/embedded-postgres" - "github.com/jiaozifs/jiaozifs/config" - "github.com/jiaozifs/jiaozifs/models" "github.com/phayes/freeport" "github.com/stretchr/testify/require" "go.uber.org/fx/fxtest" diff --git a/models/rbacmodel/actions_test.go b/models/rbacmodel/actions_test.go index d8b86119..a84c3779 100644 --- a/models/rbacmodel/actions_test.go +++ b/models/rbacmodel/actions_test.go @@ -5,7 +5,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/jiaozifs/jiaozifs/models/rbacmodel" + "github.com/GitDataAI/jiaozifs/models/rbacmodel" "golang.org/x/exp/slices" ) diff --git a/models/rbacmodel/group_test.go b/models/rbacmodel/group_test.go index 1e101830..60c629f9 100644 --- a/models/rbacmodel/group_test.go +++ b/models/rbacmodel/group_test.go @@ -5,11 +5,11 @@ import ( "testing" "time" - "github.com/jiaozifs/jiaozifs/models/rbacmodel" - "github.com/jiaozifs/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/models/rbacmodel" + "github.com/GitDataAI/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/testhelper" "github.com/brianvoe/gofakeit/v6" - "github.com/jiaozifs/jiaozifs/testhelper" "github.com/stretchr/testify/require" "github.com/google/go-cmp/cmp" diff --git a/models/rbacmodel/policies_test.go b/models/rbacmodel/policies_test.go index 4ff92099..33f2bda0 100644 --- a/models/rbacmodel/policies_test.go +++ b/models/rbacmodel/policies_test.go @@ -5,11 +5,11 @@ import ( "sort" "testing" + "github.com/GitDataAI/jiaozifs/models/rbacmodel" + "github.com/GitDataAI/jiaozifs/testhelper" "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/models/rbacmodel" - "github.com/jiaozifs/jiaozifs/testhelper" "github.com/stretchr/testify/require" ) diff --git a/models/rbacmodel/user_group_test.go b/models/rbacmodel/user_group_test.go index 1ef9d78e..4b7eeab3 100644 --- a/models/rbacmodel/user_group_test.go +++ b/models/rbacmodel/user_group_test.go @@ -4,11 +4,11 @@ import ( "context" "testing" + "github.com/GitDataAI/jiaozifs/models/rbacmodel" + "github.com/GitDataAI/jiaozifs/testhelper" "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/models/rbacmodel" - "github.com/jiaozifs/jiaozifs/testhelper" "github.com/stretchr/testify/require" ) diff --git a/models/repo.go b/models/repo.go index 899d9145..f1a053a1 100644 --- a/models/repo.go +++ b/models/repo.go @@ -4,8 +4,8 @@ import ( "context" "database/sql" + "github.com/GitDataAI/jiaozifs/models/rbacmodel" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/models/rbacmodel" "github.com/uptrace/bun" ) @@ -59,7 +59,7 @@ func (repo *PgRepo) Transaction(ctx context.Context, fn func(repo IRepo) error, for _, opt := range opts { opt(sqlOpt) } - return repo.db.RunInTx(ctx, sqlOpt, func(ctx context.Context, tx bun.Tx) error { + return repo.db.RunInTx(ctx, sqlOpt, func(_ context.Context, tx bun.Tx) error { return fn(NewRepo(tx)) }) } diff --git a/models/repo_test.go b/models/repo_test.go index aee53d2e..5784dd5c 100644 --- a/models/repo_test.go +++ b/models/repo_test.go @@ -7,10 +7,10 @@ import ( "fmt" "testing" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/testhelper" "github.com/brianvoe/gofakeit/v6" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/testhelper" "github.com/stretchr/testify/require" ) diff --git a/models/repository_test.go b/models/repository_test.go index 23ff78b3..b8296d76 100644 --- a/models/repository_test.go +++ b/models/repository_test.go @@ -5,11 +5,11 @@ import ( "testing" "time" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/testhelper" "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/testhelper" "github.com/stretchr/testify/require" ) diff --git a/models/tag.go b/models/tag.go index f48454f5..989bbf2a 100644 --- a/models/tag.go +++ b/models/tag.go @@ -4,8 +4,8 @@ import ( "context" "time" + "github.com/GitDataAI/jiaozifs/utils/hash" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/uptrace/bun" ) diff --git a/models/tag_test.go b/models/tag_test.go index 3f95f7d4..c7f94744 100644 --- a/models/tag_test.go +++ b/models/tag_test.go @@ -4,11 +4,11 @@ import ( "context" "testing" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/testhelper" "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/testhelper" "github.com/stretchr/testify/require" ) diff --git a/models/tree.go b/models/tree.go index f0caf382..07740852 100644 --- a/models/tree.go +++ b/models/tree.go @@ -7,9 +7,9 @@ import ( "sort" "time" + "github.com/GitDataAI/jiaozifs/models/filemode" + "github.com/GitDataAI/jiaozifs/utils/hash" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/models/filemode" - "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/uptrace/bun" ) diff --git a/models/tree_test.go b/models/tree_test.go index 08223ef0..4c396fa5 100644 --- a/models/tree_test.go +++ b/models/tree_test.go @@ -4,14 +4,14 @@ import ( "context" "testing" - "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/GitDataAI/jiaozifs/utils/hash" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/models/filemode" + "github.com/GitDataAI/jiaozifs/testhelper" "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/models/filemode" - "github.com/jiaozifs/jiaozifs/testhelper" "github.com/stretchr/testify/require" ) diff --git a/models/user_test.go b/models/user_test.go index 27105dbe..be8ee6bb 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -4,11 +4,11 @@ import ( "context" "testing" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/testhelper" "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/testhelper" "github.com/stretchr/testify/require" ) diff --git a/models/wip.go b/models/wip.go index 4c4f127f..e7f06d20 100644 --- a/models/wip.go +++ b/models/wip.go @@ -4,8 +4,8 @@ import ( "context" "time" + "github.com/GitDataAI/jiaozifs/utils/hash" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/uptrace/bun" ) diff --git a/models/wip_test.go b/models/wip_test.go index 86eb1c78..01ae7d7b 100644 --- a/models/wip_test.go +++ b/models/wip_test.go @@ -4,12 +4,12 @@ import ( "context" "testing" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/testhelper" + "github.com/GitDataAI/jiaozifs/utils/hash" "github.com/brianvoe/gofakeit/v6" "github.com/google/go-cmp/cmp" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/testhelper" - "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/stretchr/testify/require" ) diff --git a/testhelper/pg.go b/testhelper/pg.go index d86d7eb3..8fea8bd3 100644 --- a/testhelper/pg.go +++ b/testhelper/pg.go @@ -6,10 +6,10 @@ import ( "os" "testing" + "github.com/GitDataAI/jiaozifs/config" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/models/migrations" embeddedpostgres "github.com/fergusstrange/embedded-postgres" - "github.com/jiaozifs/jiaozifs/config" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/models/migrations" "github.com/phayes/freeport" "github.com/stretchr/testify/require" "github.com/uptrace/bun" diff --git a/utils/httputil/endpoints.go b/utils/httputil/endpoints.go index 67f96a0b..7dd2464f 100644 --- a/utils/httputil/endpoints.go +++ b/utils/httputil/endpoints.go @@ -14,7 +14,7 @@ func SetHealthHandlerInfo(info string) { } func ServeHealth() http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { _, _ = io.WriteString(w, "alive!") if healthInfo != "" { _, _ = io.WriteString(w, " "+healthInfo) diff --git a/utils/httputil/range_test.go b/utils/httputil/range_test.go index c19b71c6..208dd771 100644 --- a/utils/httputil/range_test.go +++ b/utils/httputil/range_test.go @@ -5,7 +5,7 @@ import ( "fmt" "testing" - "github.com/jiaozifs/jiaozifs/utils/httputil" + "github.com/GitDataAI/jiaozifs/utils/httputil" ) func TestParseRange(t *testing.T) { diff --git a/utils/pathutil/hash_path.go b/utils/pathutil/hash_path.go index 1432fbde..5ddcec31 100644 --- a/utils/pathutil/hash_path.go +++ b/utils/pathutil/hash_path.go @@ -3,7 +3,7 @@ package pathutil import ( "path" - "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/GitDataAI/jiaozifs/utils/hash" ) func PathOfHash(hash hash.Hash) string { diff --git a/utils/pathutil/hash_path_test.go b/utils/pathutil/hash_path_test.go index 30c30d0a..21f33429 100644 --- a/utils/pathutil/hash_path_test.go +++ b/utils/pathutil/hash_path_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/GitDataAI/jiaozifs/utils/hash" ) func TestPathOfHash(t *testing.T) { diff --git a/version/latest.go b/version/latest.go index f369f927..0b08cdfd 100644 --- a/version/latest.go +++ b/version/latest.go @@ -14,7 +14,7 @@ import ( const ( latestVersionTimeout = 10 * time.Second - DefaultReleasesURL = "https://github.com/jiaozifs/jiaozifs/releases" + DefaultReleasesURL = "https://github.com/GitDataAI/jiaozifs/releases" githubBaseURL = "https://api.github.com/" GithubRepoOwner = "jiaozifs" diff --git a/version/latest_test.go b/version/latest_test.go index cfe1b2a7..6a85f93b 100644 --- a/version/latest_test.go +++ b/version/latest_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/jiaozifs/jiaozifs/version" + "github.com/GitDataAI/jiaozifs/version" "github.com/stretchr/testify/require" ) diff --git a/versionmgr/adapter_from_config.go b/versionmgr/adapter_from_config.go index 35e2f5f7..50f0ecd1 100644 --- a/versionmgr/adapter_from_config.go +++ b/versionmgr/adapter_from_config.go @@ -4,9 +4,9 @@ import ( "context" "encoding/json" - "github.com/jiaozifs/jiaozifs/block" - "github.com/jiaozifs/jiaozifs/block/factory" - "github.com/jiaozifs/jiaozifs/config" + "github.com/GitDataAI/jiaozifs/block" + "github.com/GitDataAI/jiaozifs/block/factory" + "github.com/GitDataAI/jiaozifs/config" ) func AdapterFromConfig(ctx context.Context, jsonParams string) (block.Adapter, error) { diff --git a/versionmgr/changes.go b/versionmgr/changes.go index d60c425e..ae4fb5f1 100644 --- a/versionmgr/changes.go +++ b/versionmgr/changes.go @@ -8,8 +8,8 @@ import ( "sort" "strings" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie/noder" ) var ( @@ -225,9 +225,9 @@ func (cw *ChangesPairIter) isConflict(left, right IChange) (bool, error) { case merkletrie.Insert: switch rightAction { case merkletrie.Delete: - return false, fmt.Errorf("%s right should never be Delete while the left diff is Insert, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues %w", left.Path(), ErrActionNotMatch) + return false, fmt.Errorf("%s right should never be Delete while the left diff is Insert, must be a bug, fire issue at https://github.com/GitDataAI/jiaozifs/issues %w", left.Path(), ErrActionNotMatch) case merkletrie.Modify: - return false, fmt.Errorf("%s right should never be Modify while the left diff is Insert, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues %w", left.Path(), ErrActionNotMatch) + return false, fmt.Errorf("%s right should never be Modify while the left diff is Insert, must be a bug, fire issue at https://github.com/GitDataAI/jiaozifs/issues %w", left.Path(), ErrActionNotMatch) case merkletrie.Insert: if bytes.Equal(left.To().Hash(), right.To().Hash()) { return false, nil @@ -239,14 +239,14 @@ func (cw *ChangesPairIter) isConflict(left, right IChange) (bool, error) { case merkletrie.Delete: return false, nil case merkletrie.Insert: - return false, fmt.Errorf("%s right should never be Insert while the other diff is Delete, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues %w", left.Path(), ErrActionNotMatch) + return false, fmt.Errorf("%s right should never be Insert while the other diff is Delete, must be a bug, fire issue at https://github.com/GitDataAI/jiaozifs/issues %w", left.Path(), ErrActionNotMatch) case merkletrie.Modify: return true, nil } case merkletrie.Modify: switch rightAction { case merkletrie.Insert: - return false, fmt.Errorf("%s right should never be Insert while the other diff is Modify, must be a bug, fire issue at https://github.com/jiaozifs/jiaozifs/issues %w", left.Path(), ErrActionNotMatch) + return false, fmt.Errorf("%s right should never be Insert while the other diff is Modify, must be a bug, fire issue at https://github.com/GitDataAI/jiaozifs/issues %w", left.Path(), ErrActionNotMatch) case merkletrie.Delete: return true, nil case merkletrie.Modify: diff --git a/versionmgr/changes_test.go b/versionmgr/changes_test.go index 00a87852..7820c726 100644 --- a/versionmgr/changes_test.go +++ b/versionmgr/changes_test.go @@ -8,9 +8,9 @@ import ( "strings" "testing" - "github.com/jiaozifs/jiaozifs/utils/hash" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" + "github.com/GitDataAI/jiaozifs/utils/hash" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie/noder" "github.com/stretchr/testify/require" ) diff --git a/versionmgr/commit_node.go b/versionmgr/commit_node.go index f09152c8..b552de8e 100644 --- a/versionmgr/commit_node.go +++ b/versionmgr/commit_node.go @@ -5,9 +5,9 @@ import ( "errors" "io" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/utils/hash" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/utils/hash" ) var ( diff --git a/versionmgr/commit_node_test.go b/versionmgr/commit_node_test.go index 6fc762f0..d0ba927d 100644 --- a/versionmgr/commit_node_test.go +++ b/versionmgr/commit_node_test.go @@ -6,11 +6,11 @@ import ( "github.com/stretchr/testify/require" + "github.com/GitDataAI/jiaozifs/utils/hash" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/utils/hash" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/testhelper" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/testhelper" ) // TestWrapCommitNode diff --git a/versionmgr/commit_walker.go b/versionmgr/commit_walker.go index f4b0054f..d7fe6a56 100644 --- a/versionmgr/commit_walker.go +++ b/versionmgr/commit_walker.go @@ -5,7 +5,7 @@ import ( "errors" "io" - "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/GitDataAI/jiaozifs/utils/hash" ) type commitPreIterator struct { diff --git a/versionmgr/commit_walker_bfs.go b/versionmgr/commit_walker_bfs.go index f3727414..6db960c4 100644 --- a/versionmgr/commit_walker_bfs.go +++ b/versionmgr/commit_walker_bfs.go @@ -5,7 +5,7 @@ import ( "errors" "io" - "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/GitDataAI/jiaozifs/utils/hash" ) type bfsCommitIterator struct { diff --git a/versionmgr/commit_walker_bfs_filtered.go b/versionmgr/commit_walker_bfs_filtered.go index d185d5a0..b405b84f 100644 --- a/versionmgr/commit_walker_bfs_filtered.go +++ b/versionmgr/commit_walker_bfs_filtered.go @@ -5,7 +5,7 @@ import ( "errors" "io" - "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/GitDataAI/jiaozifs/utils/hash" ) // NewFilterCommitIter returns a CommitIter that walks the commit history, diff --git a/versionmgr/commit_walker_bfs_filtered_test.go b/versionmgr/commit_walker_bfs_filtered_test.go index b807188c..a812a025 100644 --- a/versionmgr/commit_walker_bfs_filtered_test.go +++ b/versionmgr/commit_walker_bfs_filtered_test.go @@ -6,10 +6,10 @@ import ( "errors" "testing" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/testhelper" + "github.com/GitDataAI/jiaozifs/utils/hash" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/testhelper" - "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/stretchr/testify/require" ) diff --git a/versionmgr/commit_walker_bfs_test.go b/versionmgr/commit_walker_bfs_test.go index 4d9bfdc2..cd18bcd1 100644 --- a/versionmgr/commit_walker_bfs_test.go +++ b/versionmgr/commit_walker_bfs_test.go @@ -6,10 +6,10 @@ import ( "errors" "testing" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/testhelper" + "github.com/GitDataAI/jiaozifs/utils/hash" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/testhelper" - "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/stretchr/testify/require" ) diff --git a/versionmgr/commit_walker_ctime.go b/versionmgr/commit_walker_ctime.go index 6ec6a812..c5d28b8f 100644 --- a/versionmgr/commit_walker_ctime.go +++ b/versionmgr/commit_walker_ctime.go @@ -5,7 +5,7 @@ import ( "errors" "io" - "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/GitDataAI/jiaozifs/utils/hash" "github.com/emirpasic/gods/trees/binaryheap" ) diff --git a/versionmgr/commit_walker_ctime_test.go b/versionmgr/commit_walker_ctime_test.go index a6b80fd4..77b3e510 100644 --- a/versionmgr/commit_walker_ctime_test.go +++ b/versionmgr/commit_walker_ctime_test.go @@ -7,10 +7,10 @@ import ( "testing" "time" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/testhelper" + "github.com/GitDataAI/jiaozifs/utils/hash" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/testhelper" - "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/stretchr/testify/require" ) diff --git a/versionmgr/commit_walker_test.go b/versionmgr/commit_walker_test.go index 58e8407a..9d360c3a 100644 --- a/versionmgr/commit_walker_test.go +++ b/versionmgr/commit_walker_test.go @@ -6,10 +6,10 @@ import ( "errors" "testing" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/testhelper" + "github.com/GitDataAI/jiaozifs/utils/hash" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/testhelper" - "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/stretchr/testify/require" ) diff --git a/versionmgr/conflict.go b/versionmgr/conflict.go index 4194a69d..c42ce589 100644 --- a/versionmgr/conflict.go +++ b/versionmgr/conflict.go @@ -4,8 +4,8 @@ import ( "bytes" "fmt" - "github.com/jiaozifs/jiaozifs/utils/hash" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" + "github.com/GitDataAI/jiaozifs/utils/hash" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie" ) // ConflictResolver resolve conflict between two change diff --git a/versionmgr/conflict_test.go b/versionmgr/conflict_test.go index 5f62024f..77dd31aa 100644 --- a/versionmgr/conflict_test.go +++ b/versionmgr/conflict_test.go @@ -3,7 +3,7 @@ package versionmgr import ( "testing" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie" "github.com/stretchr/testify/require" ) diff --git a/versionmgr/merge_base_test.go b/versionmgr/merge_base_test.go index a17e546e..17750c25 100644 --- a/versionmgr/merge_base_test.go +++ b/versionmgr/merge_base_test.go @@ -6,10 +6,10 @@ import ( "testing" "time" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/testhelper" + "github.com/GitDataAI/jiaozifs/utils/hash" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/testhelper" - "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/stretchr/testify/require" ) diff --git a/versionmgr/merkletrie/change.go b/versionmgr/merkletrie/change.go index 1d9de000..55e4d2f8 100644 --- a/versionmgr/merkletrie/change.go +++ b/versionmgr/merkletrie/change.go @@ -4,7 +4,7 @@ import ( "fmt" "io" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie/noder" ) // Action values represent the kind of things a Change can represent: diff --git a/versionmgr/merkletrie/change_test.go b/versionmgr/merkletrie/change_test.go index 349142c0..ce44b1ac 100644 --- a/versionmgr/merkletrie/change_test.go +++ b/versionmgr/merkletrie/change_test.go @@ -1,9 +1,9 @@ package merkletrie_test import ( - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/internal/fsnoder" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie/internal/fsnoder" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie/noder" . "gopkg.in/check.v1" //nolint ) diff --git a/versionmgr/merkletrie/difftree.go b/versionmgr/merkletrie/difftree.go index 062f0fb8..a338fce9 100644 --- a/versionmgr/merkletrie/difftree.go +++ b/versionmgr/merkletrie/difftree.go @@ -252,7 +252,7 @@ import ( "errors" "fmt" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie/noder" ) var ( diff --git a/versionmgr/merkletrie/difftree_test.go b/versionmgr/merkletrie/difftree_test.go index 87d07855..6b361958 100644 --- a/versionmgr/merkletrie/difftree_test.go +++ b/versionmgr/merkletrie/difftree_test.go @@ -10,8 +10,8 @@ import ( "testing" "unicode" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/internal/fsnoder" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie/internal/fsnoder" . "gopkg.in/check.v1" //nolint ) diff --git a/versionmgr/merkletrie/doubleiter.go b/versionmgr/merkletrie/doubleiter.go index a86b5915..2b2b3e79 100644 --- a/versionmgr/merkletrie/doubleiter.go +++ b/versionmgr/merkletrie/doubleiter.go @@ -4,7 +4,7 @@ import ( "fmt" "io" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie/noder" ) // A doubleIter is a convenience type to keep track of the current diff --git a/versionmgr/merkletrie/internal/frame/frame.go b/versionmgr/merkletrie/internal/frame/frame.go index f6a47b54..bf7c0a7d 100644 --- a/versionmgr/merkletrie/internal/frame/frame.go +++ b/versionmgr/merkletrie/internal/frame/frame.go @@ -6,7 +6,7 @@ import ( "sort" "strings" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie/noder" ) // A Frame is a collection of siblings in a trie, sorted alphabetically diff --git a/versionmgr/merkletrie/internal/frame/frame_test.go b/versionmgr/merkletrie/internal/frame/frame_test.go index 257f4656..2bc4dcf7 100644 --- a/versionmgr/merkletrie/internal/frame/frame_test.go +++ b/versionmgr/merkletrie/internal/frame/frame_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/internal/fsnoder" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie/internal/fsnoder" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie/noder" . "gopkg.in/check.v1" //nolint ) diff --git a/versionmgr/merkletrie/internal/fsnoder/dir.go b/versionmgr/merkletrie/internal/fsnoder/dir.go index 771e97a6..88efed6f 100644 --- a/versionmgr/merkletrie/internal/fsnoder/dir.go +++ b/versionmgr/merkletrie/internal/fsnoder/dir.go @@ -7,7 +7,7 @@ import ( "sort" "strings" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie/noder" ) // Dir values implement directory-like noders. diff --git a/versionmgr/merkletrie/internal/fsnoder/dir_test.go b/versionmgr/merkletrie/internal/fsnoder/dir_test.go index a1518c23..f0132a72 100644 --- a/versionmgr/merkletrie/internal/fsnoder/dir_test.go +++ b/versionmgr/merkletrie/internal/fsnoder/dir_test.go @@ -4,7 +4,7 @@ import ( "reflect" "sort" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie/noder" . "gopkg.in/check.v1" //nolint ) diff --git a/versionmgr/merkletrie/internal/fsnoder/file.go b/versionmgr/merkletrie/internal/fsnoder/file.go index 10bdae55..b2f55cd7 100644 --- a/versionmgr/merkletrie/internal/fsnoder/file.go +++ b/versionmgr/merkletrie/internal/fsnoder/file.go @@ -5,7 +5,7 @@ import ( "fmt" "hash/fnv" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie/noder" ) // file values represent file-like noders in a merkle trie. diff --git a/versionmgr/merkletrie/internal/fsnoder/file_test.go b/versionmgr/merkletrie/internal/fsnoder/file_test.go index 96fab96c..6d70acae 100644 --- a/versionmgr/merkletrie/internal/fsnoder/file_test.go +++ b/versionmgr/merkletrie/internal/fsnoder/file_test.go @@ -3,7 +3,7 @@ package fsnoder import ( "testing" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie/noder" . "gopkg.in/check.v1" //nolint ) diff --git a/versionmgr/merkletrie/internal/fsnoder/new.go b/versionmgr/merkletrie/internal/fsnoder/new.go index 188086ac..37a590f3 100644 --- a/versionmgr/merkletrie/internal/fsnoder/new.go +++ b/versionmgr/merkletrie/internal/fsnoder/new.go @@ -5,7 +5,7 @@ import ( "fmt" "io" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie/noder" ) // New function creates a full merkle trie from the string description of diff --git a/versionmgr/merkletrie/internal/fsnoder/new_test.go b/versionmgr/merkletrie/internal/fsnoder/new_test.go index 3e03e576..e75b1e3a 100644 --- a/versionmgr/merkletrie/internal/fsnoder/new_test.go +++ b/versionmgr/merkletrie/internal/fsnoder/new_test.go @@ -1,7 +1,7 @@ package fsnoder import ( - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie/noder" . "gopkg.in/check.v1" //nolint ) diff --git a/versionmgr/merkletrie/iter.go b/versionmgr/merkletrie/iter.go index 7636de14..1f95e44f 100644 --- a/versionmgr/merkletrie/iter.go +++ b/versionmgr/merkletrie/iter.go @@ -4,8 +4,8 @@ import ( "fmt" "io" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/internal/frame" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie/internal/frame" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie/noder" ) // Iter is an iterator for merkletries (only the trie part of the diff --git a/versionmgr/merkletrie/iter_test.go b/versionmgr/merkletrie/iter_test.go index 6058c0a0..2ae15a8c 100644 --- a/versionmgr/merkletrie/iter_test.go +++ b/versionmgr/merkletrie/iter_test.go @@ -5,9 +5,9 @@ import ( "io" "strings" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/internal/fsnoder" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie/internal/fsnoder" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie/noder" . "gopkg.in/check.v1" //nolint ) diff --git a/versionmgr/tree_node.go b/versionmgr/tree_node.go index 2e952bb1..bad349f8 100644 --- a/versionmgr/tree_node.go +++ b/versionmgr/tree_node.go @@ -5,8 +5,8 @@ import ( "context" "fmt" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie/noder" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie/noder" ) var _ noder.Noder = (*TreeNode)(nil) diff --git a/versionmgr/work_repo.go b/versionmgr/work_repo.go index 656c9291..b06b2bd2 100644 --- a/versionmgr/work_repo.go +++ b/versionmgr/work_repo.go @@ -9,18 +9,18 @@ import ( "os" "time" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie" - "github.com/jiaozifs/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/block" + "github.com/GitDataAI/jiaozifs/block/factory" + "github.com/GitDataAI/jiaozifs/block/params" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/utils/hash" + "github.com/GitDataAI/jiaozifs/utils/httputil" + "github.com/GitDataAI/jiaozifs/utils/pathutil" logging "github.com/ipfs/go-log/v2" - "github.com/jiaozifs/jiaozifs/block" - "github.com/jiaozifs/jiaozifs/block/factory" - "github.com/jiaozifs/jiaozifs/block/params" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/utils/hash" - "github.com/jiaozifs/jiaozifs/utils/httputil" - "github.com/jiaozifs/jiaozifs/utils/pathutil" ) var workRepoLog = logging.Logger("work_repo") diff --git a/versionmgr/work_repo_diff_test.go b/versionmgr/work_repo_diff_test.go index fe5add3e..e9716d17 100644 --- a/versionmgr/work_repo_diff_test.go +++ b/versionmgr/work_repo_diff_test.go @@ -4,11 +4,11 @@ import ( "context" "testing" - "github.com/jiaozifs/jiaozifs/block/mem" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/testhelper" - "github.com/jiaozifs/jiaozifs/utils/hash" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" + "github.com/GitDataAI/jiaozifs/block/mem" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/testhelper" + "github.com/GitDataAI/jiaozifs/utils/hash" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie" "github.com/stretchr/testify/require" ) diff --git a/versionmgr/work_repo_merge_test.go b/versionmgr/work_repo_merge_test.go index 4a04c3a4..76a0a190 100644 --- a/versionmgr/work_repo_merge_test.go +++ b/versionmgr/work_repo_merge_test.go @@ -4,10 +4,10 @@ import ( "context" "testing" - "github.com/jiaozifs/jiaozifs/block/mem" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/testhelper" - "github.com/jiaozifs/jiaozifs/utils/hash" + "github.com/GitDataAI/jiaozifs/block/mem" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/testhelper" + "github.com/GitDataAI/jiaozifs/utils/hash" "github.com/stretchr/testify/require" ) diff --git a/versionmgr/work_repo_test.go b/versionmgr/work_repo_test.go index bd0b86c5..c25c8376 100644 --- a/versionmgr/work_repo_test.go +++ b/versionmgr/work_repo_test.go @@ -12,15 +12,15 @@ import ( "testing" "time" + "github.com/GitDataAI/jiaozifs/block/mem" + "github.com/GitDataAI/jiaozifs/config" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/models/filemode" + "github.com/GitDataAI/jiaozifs/testhelper" + "github.com/GitDataAI/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/utils/hash" "github.com/brianvoe/gofakeit/v6" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/block/mem" - "github.com/jiaozifs/jiaozifs/config" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/models/filemode" - "github.com/jiaozifs/jiaozifs/testhelper" - "github.com/jiaozifs/jiaozifs/utils" - "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/versionmgr/worktree.go b/versionmgr/worktree.go index 39370806..d6883b2b 100644 --- a/versionmgr/worktree.go +++ b/versionmgr/worktree.go @@ -8,11 +8,11 @@ import ( "strings" "time" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/models/filemode" + "github.com/GitDataAI/jiaozifs/utils/hash" + "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/models/filemode" - "github.com/jiaozifs/jiaozifs/utils/hash" - "github.com/jiaozifs/jiaozifs/versionmgr/merkletrie" "golang.org/x/exp/slices" ) diff --git a/versionmgr/worktree_test.go b/versionmgr/worktree_test.go index 6c6b1899..23253f27 100644 --- a/versionmgr/worktree_test.go +++ b/versionmgr/worktree_test.go @@ -5,11 +5,11 @@ import ( "errors" "testing" + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/testhelper" + "github.com/GitDataAI/jiaozifs/utils/hash" "github.com/brianvoe/gofakeit/v6" "github.com/google/uuid" - "github.com/jiaozifs/jiaozifs/models" - "github.com/jiaozifs/jiaozifs/testhelper" - "github.com/jiaozifs/jiaozifs/utils/hash" "github.com/stretchr/testify/require" ) From 0cc85671cfcff225b76909b4829ec4830a4b660f Mon Sep 17 00:00:00 2001 From: taoshengshi Date: Wed, 13 Mar 2024 11:22:43 +0800 Subject: [PATCH 193/210] Feat/readme (#139) * update usecases * minor fix & align the content with jiaozifs.com --- README.md | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 060e5178..4a9946aa 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,15 @@ + # JiaoziFS (JZFS) A version control file system for data centric applications & teams.

- - + + -
+

- + ---- ### What is JiaoziFS? @@ -19,7 +20,7 @@ Note: * JiaoziFS is yet another implementation of [IPFS (InterPlanetary File System)](https://ipfs.tech/) as JiaoziFS will be compatible with the [implementation requirements](https://specs.ipfs.tech/architecture/principles/#ipfs-implementation-requirements) of IPFS. * As a filesystem of data versioning at scale, although JiaoziFS is built for machine learning, It has a wide range of use scenarios (refer A Universe of Uses) and can be seamlessly integrated into all your data stack. -Data-centric AI is about the practice of iterating and collaborating on data, used to build AI systems, programmatically. Machine learning pioneer Andrew Ng [argues that focusing on the quality of data fueling AI systems will help unlock its full power](https://youtu.be/TU6u_T-s68Y). +Data-centric AI is about the practice of iterating and collaborating on data, used to build AI systems, programmatically. Machine learning pioneer Andrew Ng [argues that focusing on the quality of data fueling AI systems will help unlock its full power](https://youtu.be/TU6u_T-s68Y). ---- ### Why JiaoziFS? @@ -38,13 +39,15 @@ JiaoziFS's versatility shines across different industries – making it the mult * **Industrial Digital Twin**: Developing digital twins for manufacturing involves managing tons of large files and multiple iterations of a project. All of the data collected and created in the digital twin process (and there is a lot of it) needs to be managed carefully. JiaoziFS allows you to manage changes to files over time and store these modifications in a database. * **Data Lake Management**: Data lakes are dynamic. New files and new versions of ex- isting files enter the lake at the ingestion stage. Additionally, extractors can evolve over time and generate new versions of raw data. As a result, data lake versioning is a cross-cutting concern across all stages of a data lake. Of course vanilla dis- tributed file systems are not adequate for versioning-related operations. For example, simply storing all versions may be too costly for large datasets, and without a good version manager, just using filenames to track versions can be error-prone. In a data lake, for which there are usually many users, it is even more important to clearly maintain correct versions being used and evolving across different users. Furthermore, as the number of versions increases, efficiently and cost-effectively providing storage and retrieval of versions is going to be an important feature of a successful data lake system. ---- -### Spec -[JiaoziFS Specification](https://github.com/jiaozifs/Spec) +### Specification + +[JiaoziFS Specification](https://github.com/GitDataAI/Specification/blob/main/JiaoziFS) ---- ### Basic Build And Usage #### Requirement + 1. To build JiaoziFS, you need a working installation of [Go 1.22.0 or higher](https://golang.org/dl/) 2. JiaoziFS use postgres to store running data, you can install at [postgres install installation guide](https://www.postgresql.org/docs/current/installation.html) @@ -66,21 +69,28 @@ After following the above steps, you should be able to see an executable file na ``` #### run with docker + ```bash docker run -v :/app -p 34913:34913 gitdatateam/jzfs:latest --db "postgres://:@192.168.1.16:5432/jiaozifs?sslmode=disable" --bs_path /app/data --listen http://0.0.0.0:34913 --config /app/config.toml ``` ---- ### Cloud + [Try without installing](https://cloud.jiaozifs.com) ---- ### Contributors - + + + + + ---- ### License Dual-licensed under [MIT](https://github.com/GitDataAI/jiaozifs/blob/main/LICENSE-MIT) + [Apache 2.0](https://github.com/GitDataAI/jiaozifs/blob/main/LICENSE-APACHE) + From fdff8c82a83d5f2af4332e08e126f728b18c4035 Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Fri, 15 Mar 2024 20:11:42 +0800 Subject: [PATCH 194/210] feat: get files by pattern (#141) --- api/jiaozifs.gen.go | 518 +++++++++++++++++++++----- api/swagger.yml | 66 ++++ controller/object_ctl.go | 55 +++ go.mod | 1 + go.sum | 2 + integrationtest/commit_test.go | 10 +- integrationtest/helper_test.go | 6 +- integrationtest/merge_request_test.go | 4 +- integrationtest/objects_test.go | 90 ++++- integrationtest/repo_test.go | 6 +- integrationtest/wip_object_test.go | 4 +- makefile | 2 +- versionmgr/files_walk.go | 58 +++ versionmgr/files_walk_test.go | 64 ++++ versionmgr/worktree.go | 37 +- versionmgr/worktree_test.go | 80 ++++ 16 files changed, 888 insertions(+), 115 deletions(-) create mode 100644 versionmgr/files_walk.go create mode 100644 versionmgr/files_walk_test.go diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 4c615913..78f80008 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -483,6 +483,18 @@ type UploadObjectParams struct { Path string `form:"path" json:"path"` } +// GetFilesParams defines parameters for GetFiles. +type GetFilesParams struct { + // Pattern glob pattern for match file path + Pattern *string `form:"pattern,omitempty" json:"pattern,omitempty"` + + // Type files to retrieve from wip/branch/tag/commit, default branch + Type RefType `form:"type" json:"type"` + + // RefName branch/tag to the ref + RefName string `form:"refName" json:"refName"` +} + // ListPublicRepositoryParams defines parameters for ListPublicRepository. type ListPublicRepositoryParams struct { // Prefix return items prefixed with this value @@ -556,6 +568,12 @@ type GetEntriesInRefParams struct { // Type type indicate to retrieve from wip/branch/tag/commit, default branch Type RefType `form:"type" json:"type"` + + // Recursive recursive get entries (include sub files) + Recursive *bool `form:"recursive,omitempty" json:"recursive,omitempty"` + + // Pattern pattern to get files + Pattern *bool `form:"pattern,omitempty" json:"pattern,omitempty"` } // RevokeMemberParams defines parameters for RevokeMember. @@ -811,6 +829,9 @@ type ClientInterface interface { // UploadObjectWithBody request with any body UploadObjectWithBody(ctx context.Context, owner string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetFiles request + GetFiles(ctx context.Context, owner string, repository string, params *GetFilesParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // ListPublicRepository request ListPublicRepository(ctx context.Context, params *ListPublicRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -1047,6 +1068,18 @@ func (c *Client) UploadObjectWithBody(ctx context.Context, owner string, reposit return c.Client.Do(req) } +func (c *Client) GetFiles(ctx context.Context, owner string, repository string, params *GetFilesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetFilesRequest(c.Server, owner, repository, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) ListPublicRepository(ctx context.Context, params *ListPublicRepositoryParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewListPublicRepositoryRequest(c.Server, params) if err != nil { @@ -2069,6 +2102,93 @@ func NewUploadObjectRequestWithBody(server string, owner string, repository stri return req, nil } +// NewGetFilesRequest generates requests for GetFiles +func NewGetFilesRequest(server string, owner string, repository string, params *GetFilesParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/object/%s/%s/files", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Pattern != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "pattern", runtime.ParamLocationQuery, *params.Pattern); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "type", runtime.ParamLocationQuery, params.Type); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewListPublicRepositoryRequest generates requests for ListPublicRepository func NewListPublicRepositoryRequest(server string, params *ListPublicRepositoryParams) (*http.Request, error) { var err error @@ -2890,6 +3010,38 @@ func NewGetEntriesInRefRequest(server string, owner string, repository string, p } } + if params.Recursive != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "recursive", runtime.ParamLocationQuery, *params.Recursive); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Pattern != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "pattern", runtime.ParamLocationQuery, *params.Pattern); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + queryURL.RawQuery = queryValues.Encode() } @@ -4640,6 +4792,9 @@ type ClientWithResponsesInterface interface { // UploadObjectWithBodyWithResponse request with any body UploadObjectWithBodyWithResponse(ctx context.Context, owner string, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) + // GetFilesWithResponse request + GetFilesWithResponse(ctx context.Context, owner string, repository string, params *GetFilesParams, reqEditors ...RequestEditorFn) (*GetFilesResponse, error) + // ListPublicRepositoryWithResponse request ListPublicRepositoryWithResponse(ctx context.Context, params *ListPublicRepositoryParams, reqEditors ...RequestEditorFn) (*ListPublicRepositoryResponse, error) @@ -4930,6 +5085,28 @@ func (r UploadObjectResponse) StatusCode() int { return 0 } +type GetFilesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]string +} + +// Status returns HTTPResponse.Status +func (r GetFilesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetFilesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type ListPublicRepositoryResponse struct { Body []byte HTTPResponse *http.Response @@ -5891,6 +6068,15 @@ func (c *ClientWithResponses) UploadObjectWithBodyWithResponse(ctx context.Conte return ParseUploadObjectResponse(rsp) } +// GetFilesWithResponse request returning *GetFilesResponse +func (c *ClientWithResponses) GetFilesWithResponse(ctx context.Context, owner string, repository string, params *GetFilesParams, reqEditors ...RequestEditorFn) (*GetFilesResponse, error) { + rsp, err := c.GetFiles(ctx, owner, repository, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetFilesResponse(rsp) +} + // ListPublicRepositoryWithResponse request returning *ListPublicRepositoryResponse func (c *ClientWithResponses) ListPublicRepositoryWithResponse(ctx context.Context, params *ListPublicRepositoryParams, reqEditors ...RequestEditorFn) (*ListPublicRepositoryResponse, error) { rsp, err := c.ListPublicRepository(ctx, params, reqEditors...) @@ -6466,6 +6652,32 @@ func ParseUploadObjectResponse(rsp *http.Response) (*UploadObjectResponse, error return response, nil } +// ParseGetFilesResponse parses an HTTP response from a GetFilesWithResponse call +func ParseGetFilesResponse(rsp *http.Response) (*GetFilesResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetFilesResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []string + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseListPublicRepositoryResponse parses an HTTP response from a ListPublicRepositoryWithResponse call func ParseListPublicRepositoryResponse(rsp *http.Response) (*ListPublicRepositoryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -7435,6 +7647,9 @@ type ServerInterface interface { // (POST /object/{owner}/{repository}) UploadObject(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params UploadObjectParams) + // get files by pattern + // (GET /object/{owner}/{repository}/files) + GetFiles(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetFilesParams) // list public repository in all system // (GET /repos/public) ListPublicRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, params ListPublicRepositoryParams) @@ -7605,6 +7820,12 @@ func (_ Unimplemented) UploadObject(ctx context.Context, w *JiaozifsResponse, r w.WriteHeader(http.StatusNotImplemented) } +// get files by pattern +// (GET /object/{owner}/{repository}/files) +func (_ Unimplemented) GetFiles(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetFilesParams) { + w.WriteHeader(http.StatusNotImplemented) +} + // list public repository in all system // (GET /repos/public) func (_ Unimplemented) ListPublicRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, params ListPublicRepositoryParams) { @@ -8355,6 +8576,98 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R handler.ServeHTTP(w, r.WithContext(ctx)) } +// GetFiles operation middleware +func (siw *ServerInterfaceWrapper) GetFiles(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetFilesParams + + // ------------- Optional query parameter "pattern" ------------- + + err = runtime.BindQueryParameter("form", true, false, "pattern", r.URL.Query(), ¶ms.Pattern) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "pattern", Err: err}) + return + } + + // ------------- Required query parameter "type" ------------- + + if paramValue := r.URL.Query().Get("type"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "type"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "type", r.URL.Query(), ¶ms.Type) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "type", Err: err}) + return + } + + // ------------- Required query parameter "refName" ------------- + + if paramValue := r.URL.Query().Get("refName"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetFiles(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // ListPublicRepository operation middleware func (siw *ServerInterfaceWrapper) ListPublicRepository(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -9160,6 +9473,22 @@ func (siw *ServerInterfaceWrapper) GetEntriesInRef(w http.ResponseWriter, r *htt return } + // ------------- Optional query parameter "recursive" ------------- + + err = runtime.BindQueryParameter("form", true, false, "recursive", r.URL.Query(), ¶ms.Recursive) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "recursive", Err: err}) + return + } + + // ------------- Optional query parameter "pattern" ------------- + + err = runtime.BindQueryParameter("form", true, false, "pattern", r.URL.Query(), ¶ms.Pattern) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "pattern", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetEntriesInRef(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) @@ -10987,6 +11316,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/object/{owner}/{repository}", wrapper.UploadObject) }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/object/{owner}/{repository}/files", wrapper.GetFiles) + }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/repos/public", wrapper.ListPublicRepository) }) @@ -11117,98 +11449,100 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index f95232b7..16d9fbef 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -1290,6 +1290,58 @@ paths: 420: description: too many requests + /object/{owner}/{repository}/files: + parameters: + - in: path + name: owner + required: true + schema: + type: string + - in: path + name: repository + required: true + schema: + type: string + - in: query + name: refName + description: branch/tag to the ref + required: true + schema: + type: string + get: + tags: + - objects + operationId: getFiles + summary: get files by pattern + parameters: + - in: query + name: pattern + description: glob pattern for match file path + allowEmptyValue: true + schema: + type: string + - in: query + name: type + description: files to retrieve from wip/branch/tag/commit, default branch + required: true + schema: + $ref: "#/components/schemas/RefType" + responses: + 200: + description: files list + content: + application/json: + schema: + type: array + items: + type: string + 401: + description: Unauthorized + 404: + description: object not found + 420: + description: too many requests + /wip/{owner}/{repository}: parameters: - in: path @@ -1565,6 +1617,20 @@ paths: required: true schema: $ref: "#/components/schemas/RefType" + - in: query + name: recursive + description: recursive get entries (include sub files) + required: false + allowEmptyValue: true + schema: + type: boolean + - in: query + name: pattern + description: pattern to get files + required: false + allowEmptyValue: true + schema: + type: boolean responses: 200: description: commit diff --git a/controller/object_ctl.go b/controller/object_ctl.go index a6574e90..91fd82d9 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -409,3 +409,58 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes Metadata: &api.ObjectUserMetadata{}, }, http.StatusCreated) } + +func (oct ObjectController) GetFiles(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.GetFilesParams) { + owner, err := oct.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + repository, err := oct.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) + if err != nil { + w.Error(err) + return + } + + if !oct.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ListObjectsAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { + return + } + + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return + } + + workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repository, oct.Repo, oct.PublicStorageConfig) + if err != nil { + w.Error(err) + return + } + + err = workRepo.CheckOut(ctx, versionmgr.WorkRepoState(params.Type), params.RefName) + if err != nil { + w.Error(err) + return + } + + workTree, err := workRepo.RootTree(ctx) + if err != nil { + w.Error(err) + return + } + + files, err := workTree.GetFiles(ctx, utils.StringValue(params.Pattern)) + if err != nil { + w.Error(err) + return + } + + w.JSON(files) +} diff --git a/go.mod b/go.mod index be2f406e..603d2226 100644 --- a/go.mod +++ b/go.mod @@ -119,6 +119,7 @@ require ( github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect diff --git a/go.sum b/go.sum index 5f3e90e7..c9810597 100644 --- a/go.sum +++ b/go.sum @@ -247,6 +247,8 @@ github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncV github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= diff --git a/integrationtest/commit_test.go b/integrationtest/commit_test.go index 7579e348..a0925a90 100644 --- a/integrationtest/commit_test.go +++ b/integrationtest/commit_test.go @@ -128,7 +128,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("commit kitty first changes", func(_ convey.C) { - commitWip(ctx, client, userName, repoName, branchName, "test") + _ = commitWip(ctx, client, userName, repoName, branchName, "test") }) c.Convey("get branch entries", func(c convey.C) { @@ -232,7 +232,7 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { createWip(ctx, client, userName, repoName, "main") uploadObject(ctx, client, userName, repoName, "main", "a.dat") //delete\ uploadObject(ctx, client, userName, repoName, "main", "g/m.dat") //modify - commitWip(ctx, client, userName, repoName, "main", "test") + _ = commitWip(ctx, client, userName, repoName, "main", "test") }) c.Convey("get commit entries", func(c convey.C) { @@ -390,10 +390,10 @@ func GetCommitChangesSpec(ctx context.Context, urlStr string) func(c convey.C) { createRepo(ctx, client, repoName, false) createWip(ctx, client, userName, repoName, "main") uploadObject(ctx, client, userName, repoName, "main", "m.dat") - commitWip(ctx, client, userName, repoName, "main", "test") + _ = commitWip(ctx, client, userName, repoName, "main", "test") uploadObject(ctx, client, userName, repoName, "main", "g/x.dat") - commitWip(ctx, client, userName, repoName, "main", "test") + _ = commitWip(ctx, client, userName, repoName, "main", "test") //delete deleteObject(ctx, client, userName, repoName, "main", "g/x.dat") @@ -404,7 +404,7 @@ func GetCommitChangesSpec(ctx context.Context, urlStr string) func(c convey.C) { //insert uploadObject(ctx, client, userName, repoName, "main", "g/m.dat") - commitWip(ctx, client, userName, repoName, "main", "test") + _ = commitWip(ctx, client, userName, repoName, "main", "test") }) c.Convey("get commit change", func(c convey.C) { diff --git a/integrationtest/helper_test.go b/integrationtest/helper_test.go index 100269e4..b3135a75 100644 --- a/integrationtest/helper_test.go +++ b/integrationtest/helper_test.go @@ -205,7 +205,7 @@ func createWip(ctx context.Context, client *api.Client, user string, repoName st return result.JSON200 } -func commitWip(ctx context.Context, client *api.Client, user string, repoName string, refName string, msg string) { +func commitWip(ctx context.Context, client *api.Client, user string, repoName string, refName string, msg string) *api.Wip { resp, err := client.CommitWip(ctx, user, repoName, &api.CommitWipParams{ RefName: refName, Msg: msg, @@ -213,6 +213,10 @@ func commitWip(ctx context.Context, client *api.Client, user string, repoName st convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + + result, err := api.ParseCommitWipResponse(resp) + convey.So(err, convey.ShouldBeNil) + return result.JSON201 } func createMergeRequest(ctx context.Context, client *api.Client, user string, repoName string, sourceBranch string, targetBranch string) *api.MergeRequest { diff --git a/integrationtest/merge_request_test.go b/integrationtest/merge_request_test.go index 5023a7ee..efdab4a0 100644 --- a/integrationtest/merge_request_test.go +++ b/integrationtest/merge_request_test.go @@ -31,7 +31,7 @@ func MergeRequestSpec(ctx context.Context, urlStr string) func(c convey.C) { _ = createBranch(ctx, client, userName, repoName, "main", branchName) _ = createWip(ctx, client, userName, repoName, branchName) _ = uploadObject(ctx, client, userName, repoName, branchName, "a.bin") - commitWip(ctx, client, userName, repoName, branchName, "test") + _ = commitWip(ctx, client, userName, repoName, branchName, "test") }) c.Convey("create merge request", func(c convey.C) { @@ -260,7 +260,7 @@ func MergeRequestSpec(ctx context.Context, urlStr string) func(c convey.C) { createBranch(ctx, client, userName, repoName, "main", branchName) createWip(ctx, client, userName, repoName, branchName) uploadObject(ctx, client, userName, repoName, branchName, fmt.Sprintf("%d.txt", i)) - commitWip(ctx, client, userName, repoName, branchName, "test") + _ = commitWip(ctx, client, userName, repoName, branchName, "test") createMergeRequest(ctx, client, userName, repoName, branchName, "main") } }) diff --git a/integrationtest/objects_test.go b/integrationtest/objects_test.go index 2dac5e0b..4814093e 100644 --- a/integrationtest/objects_test.go +++ b/integrationtest/objects_test.go @@ -8,6 +8,8 @@ import ( "io" "net/http" + "github.com/GitDataAI/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/api" apiimpl "github.com/GitDataAI/jiaozifs/api/api_impl" "github.com/GitDataAI/jiaozifs/utils/hash" @@ -28,6 +30,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { _ = createBranch(ctx, client, userName, repoName, "main", branchName) _ = createWip(ctx, client, userName, repoName, branchName) }) + c.Convey("upload object", func(c convey.C) { c.Convey("no auth", func() { re := client.RequestEditors @@ -124,7 +127,7 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { //commit object to branch c.Convey("commit object to branch", func(_ convey.C) { - commitWip(ctx, client, userName, repoName, branchName, "test commit msg") + _ = commitWip(ctx, client, userName, repoName, branchName, "test commit msg") }) c.Convey("head object", func(c convey.C) { @@ -304,5 +307,90 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(etag, convey.ShouldEqual, exectEtag) }) }) + + c.Convey("get files", func(c convey.C) { + repoName := "testGetFiles" + branchName := "ggct" + c.Convey("init", func() { + _ = createRepo(ctx, client, repoName, false) + _ = createBranch(ctx, client, userName, repoName, "main", branchName) + }) + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.GetFiles(ctx, userName, repoName, &api.GetFilesParams{ + RefName: "main", + Pattern: utils.String("*"), + Type: api.RefTypeBranch, + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("fail to get object in non exit user", func() { + resp, err := client.GetFiles(ctx, "fakeuser", repoName, &api.GetFilesParams{ + RefName: "main", + Pattern: utils.String("*"), + Type: api.RefTypeBranch, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to get object in non exit repo", func() { + resp, err := client.GetFiles(ctx, userName, "fakerepo", &api.GetFilesParams{ + RefName: "main", + Pattern: utils.String("*"), + Type: api.RefTypeBranch, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to get object in non exit branch", func() { + resp, err := client.GetFiles(ctx, userName, repoName, &api.GetFilesParams{ + RefName: "main_bak", + Pattern: utils.String("*"), + Type: api.RefTypeBranch, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("forbidden get object in others", func() { + resp, err := client.GetFiles(ctx, "jimmy", "happygo", &api.GetFilesParams{ + RefName: "main", + Pattern: utils.String("*"), + Type: api.RefTypeBranch, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("list success", func() { + _ = createWip(ctx, client, userName, repoName, branchName) + _ = uploadObject(ctx, client, userName, repoName, branchName, "a/b.txt") + _ = uploadObject(ctx, client, userName, repoName, branchName, "a/e.txt") + _ = uploadObject(ctx, client, userName, repoName, branchName, "a/g.txt") + _ = commitWip(ctx, client, userName, repoName, branchName, "wip") + + resp, err := client.GetFiles(ctx, userName, repoName, &api.GetFilesParams{ + RefName: branchName, + Pattern: utils.String("a/*"), + Type: api.RefTypeBranch, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseGetFilesResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.ShouldHaveLength(3, *result.JSON200) + convey.ShouldEqual("a/b.txt", (*result.JSON200)[0]) + convey.ShouldEqual("a/e.txt", (*result.JSON200)[1]) + convey.ShouldEqual("a/g.txt", (*result.JSON200)[2]) + }) + + }) } } diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index adbf8e9b..bdb8a6fa 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -406,7 +406,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("add commit to branch", func(_ convey.C) { createWip(ctx, client, userName, repoName, controller.DefaultBranchName) uploadObject(ctx, client, userName, repoName, controller.DefaultBranchName, "a.txt") - commitWip(ctx, client, userName, repoName, controller.DefaultBranchName, "first commit") + _ = commitWip(ctx, client, userName, repoName, controller.DefaultBranchName, "first commit") }) c.Convey("success get commits", func() { @@ -424,9 +424,9 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("add double commit to branch", func(_ convey.C) { uploadObject(ctx, client, userName, repoName, controller.DefaultBranchName, "b.txt") - commitWip(ctx, client, userName, repoName, controller.DefaultBranchName, "second commit") + _ = commitWip(ctx, client, userName, repoName, controller.DefaultBranchName, "second commit") uploadObject(ctx, client, userName, repoName, controller.DefaultBranchName, "c.txt") - commitWip(ctx, client, userName, repoName, controller.DefaultBranchName, "third commit") + _ = commitWip(ctx, client, userName, repoName, controller.DefaultBranchName, "third commit") }) c.Convey("success get commits by params", func() { diff --git a/integrationtest/wip_object_test.go b/integrationtest/wip_object_test.go index ecae89d7..430202ec 100644 --- a/integrationtest/wip_object_test.go +++ b/integrationtest/wip_object_test.go @@ -282,7 +282,7 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) c.Convey("commit changes", func() { - commitWip(ctx, client, userName, repoName, branchName, "test") + _ = commitWip(ctx, client, userName, repoName, branchName, "test") }) //ensure not exit @@ -482,7 +482,7 @@ func UpdateWipSpec(ctx context.Context, urlStr string) func(c convey.C) { //make wip base commit has value _ = uploadObject(ctx, client, userName, repoName, branchName, "a.txt") - commitWip(ctx, client, userName, repoName, branchName, "test") + _ = commitWip(ctx, client, userName, repoName, branchName, "test") _ = uploadObject(ctx, client, userName, repoName, branchName, "m.dat") _ = uploadObject(ctx, client, userName, repoName, branchName, "g/m.dat") diff --git a/makefile b/makefile index 18455200..02e88c72 100644 --- a/makefile +++ b/makefile @@ -11,7 +11,7 @@ ifneq ($(strip $(LDFLAGS)),) ldflags+=-extldflags=$(LDFLAGS) endif -GOFLAGS+=-ldflags="$(ldflags)" +GOFLAGS+=-ldflags=$(ldflags) gen-api: ./api/swagger.yml ./api/tmpls/chi $(GOGENERATE) ./api diff --git a/versionmgr/files_walk.go b/versionmgr/files_walk.go new file mode 100644 index 00000000..f2c6a5b2 --- /dev/null +++ b/versionmgr/files_walk.go @@ -0,0 +1,58 @@ +package versionmgr + +import ( + "container/list" + "context" + "errors" + "path" + + "github.com/GitDataAI/jiaozifs/models" +) + +var ErrHalt = errors.New("halt walk") + +type FileWalk struct { + object models.IFileTreeRepo + curNode *TreeNode +} + +type nodeWithPath struct { + curNode *TreeNode + path string +} + +func (wk FileWalk) Walk(ctx context.Context, fn func(path string) error) error { + cache := list.New() + cache.PushFront(nodeWithPath{wk.curNode, ""}) + for { + if cache.Len() == 0 { + break + } + curNode := cache.Front().Value.(nodeWithPath) + cache.Remove(cache.Front()) + subNodes := curNode.curNode.SubObjects() + for i := len(subNodes); i > 0; i-- { + if !subNodes[i-1].IsDir { + continue + } + treeNode, err := NewTreeNode(ctx, subNodes[i-1], wk.object) + if err != nil { + return err + } + + cache.PushFront(nodeWithPath{treeNode, path.Join(curNode.path, treeNode.Name())}) + continue + } + for i := 0; i < len(subNodes); i++ { + if subNodes[i].IsDir { + continue + } + err := fn(path.Join(curNode.path, subNodes[i].Name)) + if err != nil { + return err + } + } + } + + return nil +} diff --git a/versionmgr/files_walk_test.go b/versionmgr/files_walk_test.go new file mode 100644 index 00000000..c3a397a0 --- /dev/null +++ b/versionmgr/files_walk_test.go @@ -0,0 +1,64 @@ +package versionmgr + +import ( + "context" + "fmt" + "testing" + + "github.com/GitDataAI/jiaozifs/models" + "github.com/GitDataAI/jiaozifs/testhelper" + "github.com/brianvoe/gofakeit/v6" + "github.com/google/uuid" + "github.com/stretchr/testify/require" +) + +func TestFileWalk_Walk(t *testing.T) { + ctx := context.Background() + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() + + repoID := uuid.New() + objRepo := models.NewFileTree(db, repoID) + + workTree, err := NewWorkTree(ctx, objRepo, EmptyDirEntry) + require.NoError(t, err) + + addLeaves(ctx, t, workTree, repoID, "b.txt") + addLeaves(ctx, t, workTree, repoID, "a.txt") + + addLeaves(ctx, t, workTree, repoID, "a/c/f.txt") + addLeaves(ctx, t, workTree, repoID, "mm/f.txt") + addLeaves(ctx, t, workTree, repoID, "mm/c/f.txt") + addLeaves(ctx, t, workTree, repoID, "a/b/c.txt") + addLeaves(ctx, t, workTree, repoID, "a/b/d.txt") + addLeaves(ctx, t, workTree, repoID, "a/c/e.txt") + wk := FileWalk{ + object: objRepo, + curNode: workTree.root, + } + var paths []string + err = wk.Walk(ctx, func(path string) error { + fmt.Println(path) + paths = append(paths, path) + return nil + }) + require.NoError(t, err) + require.Equal(t, "a.txt", paths[0]) + require.Equal(t, "b.txt", paths[1]) + require.Equal(t, "a/b/c.txt", paths[2]) + require.Equal(t, "a/b/d.txt", paths[3]) + require.Equal(t, "a/c/e.txt", paths[4]) + require.Equal(t, "a/c/f.txt", paths[5]) + require.Equal(t, "mm/f.txt", paths[6]) + require.Equal(t, "mm/c/f.txt", paths[7]) +} + +func addLeaves(ctx context.Context, t *testing.T, workTree *WorkTree, repoID uuid.UUID, path string) { + blob := &models.Blob{} + require.NoError(t, gofakeit.Struct(blob)) + blob.Type = models.BlobObject + blob.RepositoryID = repoID + + err := workTree.AddLeaf(ctx, path, blob) + require.NoError(t, err) +} diff --git a/versionmgr/worktree.go b/versionmgr/worktree.go index d6883b2b..10211fb9 100644 --- a/versionmgr/worktree.go +++ b/versionmgr/worktree.go @@ -8,6 +8,8 @@ import ( "strings" "time" + "github.com/gobwas/glob" + "github.com/GitDataAI/jiaozifs/models" "github.com/GitDataAI/jiaozifs/models/filemode" "github.com/GitDataAI/jiaozifs/utils/hash" @@ -157,7 +159,7 @@ func (workTree *WorkTree) ReplaceSubTreeEntry(ctx context.Context, treeEntry mod return obj.TreeNode(), nil } -func (workTree *WorkTree) matchPath(ctx context.Context, path string) ([]FullObject, []string, error) { +func (workTree *WorkTree) findNodeByPath(ctx context.Context, path string) ([]FullObject, []string, error) { pathSegs := strings.Split(path, "/") //path must be unix style var existNodes []FullObject var missingPath []string @@ -206,7 +208,7 @@ func (workTree *WorkTree) matchPath(ctx context.Context, path string) ([]FullObj // AddLeaf insert new leaf in entry, if path not exit, create new func (workTree *WorkTree) AddLeaf(ctx context.Context, fullPath string, blob *models.Blob) error { fullPath = CleanPath(fullPath) - existNode, missingPath, err := workTree.matchPath(ctx, fullPath) + existNode, missingPath, err := workTree.findNodeByPath(ctx, fullPath) if err != nil { return err } @@ -287,7 +289,7 @@ func (workTree *WorkTree) AddLeaf(ctx context.Context, fullPath string, blob *mo // ReplaceLeaf replace leaf with a new blob, all parent directory updated func (workTree *WorkTree) ReplaceLeaf(ctx context.Context, fullPath string, blob *models.Blob) error { fullPath = CleanPath(fullPath) - existNode, missingPath, err := workTree.matchPath(ctx, fullPath) + existNode, missingPath, err := workTree.findNodeByPath(ctx, fullPath) if err != nil { return err } @@ -350,7 +352,7 @@ func (workTree *WorkTree) ReplaceLeaf(ctx context.Context, fullPath string, blob // RemoveEntry(ctx, root, "a/b") return empty root. a b c.txt d.txt all removed func (workTree *WorkTree) RemoveEntry(ctx context.Context, fullPath string) error { fullPath = CleanPath(fullPath) - existNode, missingPath, err := workTree.matchPath(ctx, fullPath) + existNode, missingPath, err := workTree.findNodeByPath(ctx, fullPath) if err != nil { return err } @@ -419,13 +421,13 @@ func (workTree *WorkTree) RemoveEntry(ctx context.Context, fullPath string) erro // // Ls(ctx, root, "a") return b // Ls(ctx, root, "a/b" return c.txt and d.txt -func (workTree *WorkTree) Ls(ctx context.Context, fullPath string) ([]FullTreeEntry, error) { - fullPath = CleanPath(fullPath) +func (workTree *WorkTree) Ls(ctx context.Context, pattern string) ([]FullTreeEntry, error) { + fullPath := CleanPath(pattern) if len(fullPath) == 0 { return workTree.getFullEntry(ctx, workTree.root.SubObjects()) } - existNode, missingPath, err := workTree.matchPath(ctx, fullPath) + existNode, missingPath, err := workTree.findNodeByPath(ctx, fullPath) if err != nil { return nil, err } @@ -441,6 +443,25 @@ func (workTree *WorkTree) Ls(ctx context.Context, fullPath string) ([]FullTreeEn return workTree.getFullEntry(ctx, lastNode.Node().SubObjects) } +func (workTree *WorkTree) GetFiles(ctx context.Context, pattern string) ([]string, error) { + //todo match all files, it maybe slow maybe need a new algo like filepath.Glob + wk := FileWalk{curNode: workTree.root, object: workTree.object} + files := make([]string, 0) + g, err := glob.Compile(pattern) + if err != nil { + return files, err + } + + err = wk.Walk(ctx, func(path string) error { + fmt.Println(path) + if g.Match(path) { + files = append(files, path) + } + return nil + }) + return files, err +} + func (workTree *WorkTree) getFullEntry(ctx context.Context, treeEntries []models.TreeEntry) ([]FullTreeEntry, error) { entries := make([]FullTreeEntry, 0) for _, entry := range treeEntries { @@ -473,7 +494,7 @@ func (workTree *WorkTree) getFullEntry(ctx context.Context, treeEntries []models func (workTree *WorkTree) FindBlob(ctx context.Context, fullPath string) (*models.Blob, string, error) { fullPath = CleanPath(fullPath) - existNode, missingPath, err := workTree.matchPath(ctx, fullPath) + existNode, missingPath, err := workTree.findNodeByPath(ctx, fullPath) if err != nil { return nil, "", err } diff --git a/versionmgr/worktree_test.go b/versionmgr/worktree_test.go index 23253f27..73fdfa33 100644 --- a/versionmgr/worktree_test.go +++ b/versionmgr/worktree_test.go @@ -146,3 +146,83 @@ func TestCleanPath(t *testing.T) { require.Equal(t, "a/b/c", CleanPath("/a/b/c/")) require.Equal(t, "a/b/c", CleanPath("\\a\\b\\c\\")) } + +func TestWorkTreeGetFiles(t *testing.T) { + ctx := context.Background() + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() + + repoID := uuid.New() + objRepo := models.NewFileTree(db, repoID) + + workTree, err := NewWorkTree(ctx, objRepo, EmptyDirEntry) + require.NoError(t, err) + + paths := []string{ + "a/b/c.txt", + "a/b/d.txt", + "ff/b/c.txt", + "ff/b/d.txt", + "ff/b/e.jpg", + "ff/b/f.jpg", + "a.txt", + } + for _, path := range paths { + blob := &models.Blob{} + require.NoError(t, gofakeit.Struct(blob)) + blob.Type = models.BlobObject + blob.RepositoryID = repoID + err = workTree.AddLeaf(ctx, path, blob) + require.NoError(t, err) + } + + t.Run("all", func(t *testing.T) { + newPaths, err := workTree.GetFiles(ctx, "*") + require.NoError(t, err) + require.Equal(t, 7, len(newPaths)) + require.Equal(t, "a.txt", newPaths[0]) + require.Equal(t, "a/b/c.txt", newPaths[1]) + require.Equal(t, "a/b/d.txt", newPaths[2]) + require.Equal(t, "ff/b/c.txt", newPaths[3]) + require.Equal(t, "ff/b/d.txt", newPaths[4]) + require.Equal(t, "ff/b/e.jpg", newPaths[5]) + require.Equal(t, "ff/b/f.jpg", newPaths[6]) + }) + + t.Run("single file", func(t *testing.T) { + newPaths, err := workTree.GetFiles(ctx, "a/b/d.txt") + require.NoError(t, err) + require.Equal(t, 1, len(newPaths)) + require.Equal(t, "a/b/d.txt", newPaths[0]) + }) + + t.Run("single path", func(t *testing.T) { + newPaths, err := workTree.GetFiles(ctx, "a") + require.NoError(t, err) + require.Equal(t, 0, len(newPaths)) + }) + + t.Run("ext match", func(t *testing.T) { + newPaths, err := workTree.GetFiles(ctx, "*.jpg") + require.NoError(t, err) + require.Equal(t, 2, len(newPaths)) + require.Equal(t, "ff/b/e.jpg", newPaths[0]) + require.Equal(t, "ff/b/f.jpg", newPaths[1]) + }) + + t.Run("filename match", func(t *testing.T) { + newPaths, err := workTree.GetFiles(ctx, "*/e.jpg") + require.NoError(t, err) + require.Equal(t, 1, len(newPaths)) + require.Equal(t, "ff/b/e.jpg", newPaths[0]) + }) + + t.Run("sub", func(t *testing.T) { + newPaths, err := workTree.GetFiles(ctx, "a/*") + require.NoError(t, err) + require.Equal(t, 2, len(newPaths)) + require.Equal(t, "a/b/c.txt", newPaths[0]) + require.Equal(t, "a/b/d.txt", newPaths[1]) + }) + +} From 899193bee76f7767bc051d269e061b136e0594a9 Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Sat, 16 Mar 2024 12:44:08 +0800 Subject: [PATCH 195/210] feat: walk with node (#142) * feat: walk with node --- controller/object_ctl.go | 4 +-- versionmgr/files_walk.go | 10 +++++-- versionmgr/files_walk_test.go | 2 +- versionmgr/worktree.go | 20 ++++++++++---- versionmgr/worktree_test.go | 50 +++++++++++++++++------------------ 5 files changed, 51 insertions(+), 35 deletions(-) diff --git a/controller/object_ctl.go b/controller/object_ctl.go index 91fd82d9..8dd5f744 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -456,11 +456,11 @@ func (oct ObjectController) GetFiles(ctx context.Context, w *api.JiaozifsRespons return } - files, err := workTree.GetFiles(ctx, utils.StringValue(params.Pattern)) + treeManifest, err := workTree.GetTreeManifest(ctx, utils.StringValue(params.Pattern)) if err != nil { w.Error(err) return } - w.JSON(files) + w.JSON(treeManifest.FileList) } diff --git a/versionmgr/files_walk.go b/versionmgr/files_walk.go index f2c6a5b2..dccb8f81 100644 --- a/versionmgr/files_walk.go +++ b/versionmgr/files_walk.go @@ -21,7 +21,7 @@ type nodeWithPath struct { path string } -func (wk FileWalk) Walk(ctx context.Context, fn func(path string) error) error { +func (wk FileWalk) Walk(ctx context.Context, fn func(blob *models.Blob, path string) error) error { cache := list.New() cache.PushFront(nodeWithPath{wk.curNode, ""}) for { @@ -47,7 +47,13 @@ func (wk FileWalk) Walk(ctx context.Context, fn func(path string) error) error { if subNodes[i].IsDir { continue } - err := fn(path.Join(curNode.path, subNodes[i].Name)) + + blob, err := wk.object.Blob(ctx, subNodes[i].Hash) + if err != nil { + return err + } + + err = fn(blob, path.Join(curNode.path, subNodes[i].Name)) if err != nil { return err } diff --git a/versionmgr/files_walk_test.go b/versionmgr/files_walk_test.go index c3a397a0..52f38460 100644 --- a/versionmgr/files_walk_test.go +++ b/versionmgr/files_walk_test.go @@ -37,7 +37,7 @@ func TestFileWalk_Walk(t *testing.T) { curNode: workTree.root, } var paths []string - err = wk.Walk(ctx, func(path string) error { + err = wk.Walk(ctx, func(_ *models.Blob, path string) error { fmt.Println(path) paths = append(paths, path) return nil diff --git a/versionmgr/worktree.go b/versionmgr/worktree.go index 10211fb9..af744213 100644 --- a/versionmgr/worktree.go +++ b/versionmgr/worktree.go @@ -443,23 +443,33 @@ func (workTree *WorkTree) Ls(ctx context.Context, pattern string) ([]FullTreeEnt return workTree.getFullEntry(ctx, lastNode.Node().SubObjects) } -func (workTree *WorkTree) GetFiles(ctx context.Context, pattern string) ([]string, error) { +type TreeManifest struct { + Size int64 `json:"size"` + FileList []string `json:"file_list"` +} + +func (workTree *WorkTree) GetTreeManifest(ctx context.Context, pattern string) (TreeManifest, error) { //todo match all files, it maybe slow maybe need a new algo like filepath.Glob wk := FileWalk{curNode: workTree.root, object: workTree.object} - files := make([]string, 0) g, err := glob.Compile(pattern) if err != nil { - return files, err + return TreeManifest{}, err } - err = wk.Walk(ctx, func(path string) error { + files := make([]string, 0) + var size int64 + err = wk.Walk(ctx, func(blob *models.Blob, path string) error { fmt.Println(path) if g.Match(path) { + size += blob.Size files = append(files, path) } return nil }) - return files, err + return TreeManifest{ + Size: size, + FileList: files, + }, err } func (workTree *WorkTree) getFullEntry(ctx context.Context, treeEntries []models.TreeEntry) ([]FullTreeEntry, error) { diff --git a/versionmgr/worktree_test.go b/versionmgr/worktree_test.go index 73fdfa33..776d1dfb 100644 --- a/versionmgr/worktree_test.go +++ b/versionmgr/worktree_test.go @@ -177,52 +177,52 @@ func TestWorkTreeGetFiles(t *testing.T) { } t.Run("all", func(t *testing.T) { - newPaths, err := workTree.GetFiles(ctx, "*") + manifest, err := workTree.GetTreeManifest(ctx, "*") require.NoError(t, err) - require.Equal(t, 7, len(newPaths)) - require.Equal(t, "a.txt", newPaths[0]) - require.Equal(t, "a/b/c.txt", newPaths[1]) - require.Equal(t, "a/b/d.txt", newPaths[2]) - require.Equal(t, "ff/b/c.txt", newPaths[3]) - require.Equal(t, "ff/b/d.txt", newPaths[4]) - require.Equal(t, "ff/b/e.jpg", newPaths[5]) - require.Equal(t, "ff/b/f.jpg", newPaths[6]) + require.Equal(t, 7, len(manifest.FileList)) + require.Equal(t, "a.txt", manifest.FileList[0]) + require.Equal(t, "a/b/c.txt", manifest.FileList[1]) + require.Equal(t, "a/b/d.txt", manifest.FileList[2]) + require.Equal(t, "ff/b/c.txt", manifest.FileList[3]) + require.Equal(t, "ff/b/d.txt", manifest.FileList[4]) + require.Equal(t, "ff/b/e.jpg", manifest.FileList[5]) + require.Equal(t, "ff/b/f.jpg", manifest.FileList[6]) }) t.Run("single file", func(t *testing.T) { - newPaths, err := workTree.GetFiles(ctx, "a/b/d.txt") + manifest, err := workTree.GetTreeManifest(ctx, "a/b/d.txt") require.NoError(t, err) - require.Equal(t, 1, len(newPaths)) - require.Equal(t, "a/b/d.txt", newPaths[0]) + require.Equal(t, 1, len(manifest.FileList)) + require.Equal(t, "a/b/d.txt", manifest.FileList[0]) }) t.Run("single path", func(t *testing.T) { - newPaths, err := workTree.GetFiles(ctx, "a") + manifest, err := workTree.GetTreeManifest(ctx, "a") require.NoError(t, err) - require.Equal(t, 0, len(newPaths)) + require.Equal(t, 0, len(manifest.FileList)) }) t.Run("ext match", func(t *testing.T) { - newPaths, err := workTree.GetFiles(ctx, "*.jpg") + manifest, err := workTree.GetTreeManifest(ctx, "*.jpg") require.NoError(t, err) - require.Equal(t, 2, len(newPaths)) - require.Equal(t, "ff/b/e.jpg", newPaths[0]) - require.Equal(t, "ff/b/f.jpg", newPaths[1]) + require.Equal(t, 2, len(manifest.FileList)) + require.Equal(t, "ff/b/e.jpg", manifest.FileList[0]) + require.Equal(t, "ff/b/f.jpg", manifest.FileList[1]) }) t.Run("filename match", func(t *testing.T) { - newPaths, err := workTree.GetFiles(ctx, "*/e.jpg") + manifest, err := workTree.GetTreeManifest(ctx, "*/e.jpg") require.NoError(t, err) - require.Equal(t, 1, len(newPaths)) - require.Equal(t, "ff/b/e.jpg", newPaths[0]) + require.Equal(t, 1, len(manifest.FileList)) + require.Equal(t, "ff/b/e.jpg", manifest.FileList[0]) }) t.Run("sub", func(t *testing.T) { - newPaths, err := workTree.GetFiles(ctx, "a/*") + manifest, err := workTree.GetTreeManifest(ctx, "a/*") require.NoError(t, err) - require.Equal(t, 2, len(newPaths)) - require.Equal(t, "a/b/c.txt", newPaths[0]) - require.Equal(t, "a/b/d.txt", newPaths[1]) + require.Equal(t, 2, len(manifest.FileList)) + require.Equal(t, "a/b/c.txt", manifest.FileList[0]) + require.Equal(t, "a/b/d.txt", manifest.FileList[1]) }) } From c14cfa02ee0e74203b6502c2d7f3d3d1b71da556 Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Sat, 16 Mar 2024 17:09:56 +0800 Subject: [PATCH 196/210] fix: wrong resp for create wip (#146) --- api/jiaozifs.gen.go | 23 ++++++++++++++++------- api/swagger.yml | 6 ++++++ controller/wip_ctl.go | 1 + 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 78f80008..249d7da6 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -5871,6 +5871,7 @@ type GetWipResponse struct { Body []byte HTTPResponse *http.Response JSON200 *Wip + JSON201 *Wip } // Status returns HTTPResponse.Status @@ -7509,6 +7510,13 @@ func ParseGetWipResponse(rsp *http.Response) (*GetWipResponse, error) { } response.JSON200 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest Wip + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + } return response, nil @@ -11536,13 +11544,14 @@ var swaggerSpec = []string{ "h1ViXFdiOvzJnRfXaAxzx+eO3ZVcElg8mJm07uO6Gh3G3vW/XTGaAiR3aCldQHxSugpYozg0cGaa95Te", "rXdYLDF7Yo3p3H5M0XznKFqSiM9mEOwx/Iyu6MLWPEQ7BGO/A+qjrhZWLxNW3PDJQ+13cusQ864q31tq", "M/XyU0s7m8L6l+tcxe5Xvh/X5d/Ya1L5KzQJiOPrdq7w6YKlGxZj+8pSb7OiaQt2zx8/ERDzSyALLi5Y", - "MtPqmAqOfm0pJU1kV6ixnf2tqIfu3qEUDpLvvVRaLzGut+atnjxvdIDXuBPe7x741m5a5Cq1q0SOQqc2", - "z99ozvVmpXh2VIyt+H59Rfe6UC4vA9K1FnxlaWvdj51rzICLifMncoPcoWL5LD1AqCto2wTyHkIWertp", - "FN+SfXxlj+8Su02CjsHuTniw98ZjkJLO2kiL5eyWRQIPd+2DWD5yvw6dTUsCWTA1Jwks7sXHG3k/uL6K", - "1OsbGoYnh30r3qy01mNhwU1X1752C/5jL+D9ytLNUPcBbBkXLK3tFVPBsei7VrmVCMMTAV0BlyB6gu5/", - "gMM8an5UHUJ2RXi45lsNcxvx2QjRj3ESan7foG2umcR7g0DHcDaoVwT5XNE9S3WlIFoTE0YEtCOKwjcf", - "tbRv0Shq4uPas7vqp8Tdp3mja+dnyTH8UX6pu/6n/eZ2/WEe0cGn5dew8xNDlI9r0a4koZnFIwlSbu6u", - "ld+6PhqPI+7TaM6lOnr56q+HL8c0ZePLQ6+pwWs7LF49v/m/AAAA///zcrj5Wa4AAA==", + "MtPqmAqOfm0pJU1kV6ixnf2tqIfu3qEUDpKxdPjhrgemRK/rzeGJ/TTd/Vds6zWb60FlqwfgG50jNq6m", + "97uOvrULH7lm7yqfpNCwzdNIHHq4UUWgHdWEKz6jX9G9LrDNq5F0LUlfWdpafmTnGjPgfuT8iVxkd0Gd", + "lf8DhLqCtk0g7yEkw7ebRvFJ28dXffkusdvkCRns7oQHe309BinprI20WM5uWatw5x6J5SN3L9HntSSQ", + "BVNz47Dcg6s58n5wfZyp16c8DE8O+1a8WfCtx8KCe7+u7fUW3NhewPuVpZuh7gPYuS5YWtuypoJj7Xmt", + "ciuBjicCugIuQfQE3f8Ah3nU/LY7hOyK8HDNJyPmNvC0EaIf4yTU/L5Bu20zifcGgY7hbGyxiDW6goyW", + "6kpdtiYmjAhoRxSFb76tad+iUdTEx7VHiNUvmrsPFUfXzq+jYxSm/GB4/U/76e/6wzywhE/Lj3LnB5co", + "H9eiXcmFM4tHEqTcXKErP7l9NB5H3KfRnEt19PLVXw9fjmnKxpeHXlOD13ZYvHp+838BAAD//28XbVvg", + "rgAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 16d9fbef..926816f4 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -1372,6 +1372,12 @@ paths: application/json: schema: $ref: "#/components/schemas/Wip" + 201: + description: a new working in process created + content: + application/json: + schema: + $ref: "#/components/schemas/Wip" 400: description: ValidationError 401: diff --git a/controller/wip_ctl.go b/controller/wip_ctl.go index a9f8fa77..e3e7d309 100644 --- a/controller/wip_ctl.go +++ b/controller/wip_ctl.go @@ -85,6 +85,7 @@ func (wipCtl WipController) GetWip(ctx context.Context, w *api.JiaozifsResponse, } if isNew { w.JSON(wipToDto(wip), http.StatusCreated) + return } w.JSON(wipToDto(wip)) } From cf65152fa3f87a42c6faf8a45207c831cdea445c Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Sun, 17 Mar 2024 10:36:40 +0800 Subject: [PATCH 197/210] fix: remove no useful argument (#147) --- api/jiaozifs.gen.go | 243 +++++++++++++++++--------------------------- api/swagger.yml | 14 --- 2 files changed, 94 insertions(+), 163 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 249d7da6..80cfc122 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -568,12 +568,6 @@ type GetEntriesInRefParams struct { // Type type indicate to retrieve from wip/branch/tag/commit, default branch Type RefType `form:"type" json:"type"` - - // Recursive recursive get entries (include sub files) - Recursive *bool `form:"recursive,omitempty" json:"recursive,omitempty"` - - // Pattern pattern to get files - Pattern *bool `form:"pattern,omitempty" json:"pattern,omitempty"` } // RevokeMemberParams defines parameters for RevokeMember. @@ -3010,38 +3004,6 @@ func NewGetEntriesInRefRequest(server string, owner string, repository string, p } } - if params.Recursive != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "recursive", runtime.ParamLocationQuery, *params.Recursive); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - if params.Pattern != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "pattern", runtime.ParamLocationQuery, *params.Pattern); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - queryURL.RawQuery = queryValues.Encode() } @@ -9481,22 +9443,6 @@ func (siw *ServerInterfaceWrapper) GetEntriesInRef(w http.ResponseWriter, r *htt return } - // ------------- Optional query parameter "recursive" ------------- - - err = runtime.BindQueryParameter("form", true, false, "recursive", r.URL.Query(), ¶ms.Recursive) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "recursive", Err: err}) - return - } - - // ------------- Optional query parameter "pattern" ------------- - - err = runtime.BindQueryParameter("form", true, false, "pattern", r.URL.Query(), ¶ms.Pattern) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "pattern", Err: err}) - return - } - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetEntriesInRef(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) })) @@ -11457,101 +11403,100 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "rgAA", + "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", + "xc3/BQAA//+3Yo532a0AAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 926816f4..2164357d 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -1623,20 +1623,6 @@ paths: required: true schema: $ref: "#/components/schemas/RefType" - - in: query - name: recursive - description: recursive get entries (include sub files) - required: false - allowEmptyValue: true - schema: - type: boolean - - in: query - name: pattern - description: pattern to get files - required: false - allowEmptyValue: true - schema: - type: boolean responses: 200: description: commit From fac84ce7264f57d43421aa2d8504c29e61dd63dd Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Sun, 17 Mar 2024 16:06:12 +0800 Subject: [PATCH 198/210] fix: cert support (#148) --- chart/templates/ingress.yaml | 2 ++ chart/values.yaml | 1 + script/cert.yaml | 11 +++++++++++ 3 files changed, 14 insertions(+) create mode 100644 script/cert.yaml diff --git a/chart/templates/ingress.yaml b/chart/templates/ingress.yaml index b8b4c918..6f591858 100644 --- a/chart/templates/ingress.yaml +++ b/chart/templates/ingress.yaml @@ -15,6 +15,8 @@ metadata: release: jiaozifs-api spec: ingressClassName: {{.Values.ingress_name}} + tls: + - secretName: {{.Values.cert}} rules: - host: api.jiaozifs.com http: diff --git a/chart/values.yaml b/chart/values.yaml index f4b75418..3071e788 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -9,3 +9,4 @@ ingress_name: nginx log_level: info claim_name: jiaozifs-home tag: latest +cert: api-jiaozifs-com-tls diff --git a/script/cert.yaml b/script/cert.yaml new file mode 100644 index 00000000..3ea41169 --- /dev/null +++ b/script/cert.yaml @@ -0,0 +1,11 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: api-jiaozifs-com-certificate +spec: + secretName: api-jiaozifs-com-tls + issuerRef: + name: letsencrypt-prod + kind: ClusterIssuer + dnsNames: + - api.jiaozifs.com From b27b65db1c726065e393fb62790095b2a7a4e062 Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Sun, 17 Mar 2024 18:06:47 +0800 Subject: [PATCH 199/210] feat: add arg to force update object , and a command to commit object (#151) --- api/custom_response.go | 17 ++ api/custom_response_test.go | 11 ++ api/jiaozifs.gen.go | 215 +++++++++++++++----------- api/swagger.yml | 9 ++ cmd/helper.go | 28 ++-- cmd/root.go | 4 + cmd/uploadfiles.go | 139 +++++++++++++++++ cmd/version.go | 2 +- codecov.yml | 1 + controller/object_ctl.go | 24 ++- integrationtest/commit_test.go | 18 +-- integrationtest/helper_test.go | 11 +- integrationtest/merge_request_test.go | 4 +- integrationtest/objects_test.go | 33 +++- integrationtest/repo_test.go | 6 +- integrationtest/wip_object_test.go | 18 +-- 16 files changed, 399 insertions(+), 141 deletions(-) create mode 100644 cmd/uploadfiles.go diff --git a/api/custom_response.go b/api/custom_response.go index 756f2007..45956f01 100644 --- a/api/custom_response.go +++ b/api/custom_response.go @@ -3,6 +3,7 @@ package api import ( "encoding/json" "errors" + "fmt" "net/http" "github.com/GitDataAI/jiaozifs/auth" @@ -67,6 +68,13 @@ func (response *JiaozifsResponse) Error(err error) { return } + var codeErr ErrCode + if errors.As(err, &codeErr) { + response.WriteHeader(int(codeErr)) + _, _ = response.Write([]byte(err.Error())) + return + } + response.WriteHeader(http.StatusInternalServerError) _, _ = response.Write([]byte(err.Error())) } @@ -89,3 +97,12 @@ func (response *JiaozifsResponse) String(msg string, code ...int) { func (response *JiaozifsResponse) Code(code int) { response.WriteHeader(code) } + +type ErrCode int + +func NewErrCode(code int) ErrCode { + return ErrCode(code) +} +func (err ErrCode) Error() string { + return fmt.Sprintf("code %d msg %s", err, http.StatusText(int(err))) +} diff --git a/api/custom_response_test.go b/api/custom_response_test.go index a9d42eae..0154ac4b 100644 --- a/api/custom_response_test.go +++ b/api/custom_response_test.go @@ -77,6 +77,17 @@ func TestJiaozifsResponse(t *testing.T) { jzResp.Error(fmt.Errorf("mock")) }) + t.Run("err code", func(t *testing.T) { + ctrl := gomock.NewController(t) + resp := NewMockResponseWriter(ctrl) + jzResp := JiaozifsResponse{resp} + + resp.EXPECT().WriteHeader(http.StatusConflict) + resp.EXPECT().Write([]byte("mock code 409 msg Conflict")) + + jzResp.Error(fmt.Errorf("mock %w", ErrCode(http.StatusConflict))) + }) + t.Run("error not found", func(t *testing.T) { ctrl := gomock.NewController(t) resp := NewMockResponseWriter(ctrl) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 80cfc122..a07ab113 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -476,6 +476,9 @@ type UploadObjectMultipartBody struct { // UploadObjectParams defines parameters for UploadObject. type UploadObjectParams struct { + // IsReplace indicate to replace existing object or not + IsReplace *bool `form:"isReplace,omitempty" json:"isReplace,omitempty"` + // RefName branch/tag to the ref RefName string `form:"refName" json:"refName"` @@ -2059,6 +2062,22 @@ func NewUploadObjectRequestWithBody(server string, owner string, repository stri if params != nil { queryValues := queryURL.Query() + if params.IsReplace != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "isReplace", runtime.ParamLocationQuery, *params.IsReplace); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { @@ -8505,6 +8524,14 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R // Parameter object where we will unmarshal all parameters from the context var params UploadObjectParams + // ------------- Optional query parameter "isReplace" ------------- + + err = runtime.BindQueryParameter("form", true, false, "isReplace", r.URL.Query(), ¶ms.IsReplace) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "isReplace", Err: err}) + return + } + // ------------- Required query parameter "refName" ------------- if paramValue := r.URL.Query().Get("refName"); paramValue != "" { @@ -11403,100 +11430,100 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "xc3/BQAA//+3Yo532a0AAA==", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 2164357d..f94c806c 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -1239,6 +1239,13 @@ paths: tags: - objects operationId: uploadObject + parameters: + - in: query + name: isReplace + description: indicate to replace existing object or not + allowEmptyValue: true + schema: + type: boolean x-validation-exclude-body: true requestBody: content: @@ -1265,6 +1272,8 @@ paths: description: ValidationError 401: description: Unauthorized ValidationError + 409: + description: Resource Conflict 403: description: Forbidden 404: diff --git a/cmd/helper.go b/cmd/helper.go index 5c29d748..6f46f0a3 100644 --- a/cmd/helper.go +++ b/cmd/helper.go @@ -1,24 +1,24 @@ package cmd import ( + "io" + "net/http" + "github.com/GitDataAI/jiaozifs/api" - "github.com/GitDataAI/jiaozifs/config" + "github.com/spf13/cobra" ) -func GetDefaultClient() (*api.Client, error) { - swagger, err := api.GetSwagger() - if err != nil { - return nil, err - } +func GetClient(cmd *cobra.Command) (*api.Client, error) { + url := cmd.Flags().Lookup("url").Value.String() + ak := cmd.Flags().Lookup("ak").Value.String() + sk := cmd.Flags().Lookup("sk").Value.String() + return api.NewClient(url, api.AkSkOption(ak, sk)) +} - //get runtime version - cfg, err := config.LoadConfig(cfgFile) - if err != nil { - return nil, err - } - basePath, err := swagger.Servers[0].BasePath() +func tryLogError(resp *http.Response) string { + bodyContent, err := io.ReadAll(resp.Body) if err != nil { - return nil, err + return "" } - return api.NewClient(cfg.API.Listen + basePath) + return string(bodyContent) } diff --git a/cmd/root.go b/cmd/root.go index 19199866..35ef80fb 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -35,4 +35,8 @@ func init() { _ = viper.BindPFlag("api.listen", rootCmd.PersistentFlags().Lookup("listen")) _ = viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config")) _ = viper.BindPFlag("log.level", rootCmd.PersistentFlags().Lookup("log-level")) + + uploadCmd.PersistentFlags().String("url", "https://127.0.0.1:34913", "url") + uploadCmd.PersistentFlags().String("ak", "", "access key") + uploadCmd.PersistentFlags().String("sk", "", "secret key") } diff --git a/cmd/uploadfiles.go b/cmd/uploadfiles.go new file mode 100644 index 00000000..bf36b0b1 --- /dev/null +++ b/cmd/uploadfiles.go @@ -0,0 +1,139 @@ +package cmd + +import ( + "errors" + "fmt" + "io/fs" + "net/http" + "os" + path2 "path" + "path/filepath" + "strings" + + "github.com/GitDataAI/jiaozifs/utils" + + "github.com/GitDataAI/jiaozifs/api" + + "github.com/spf13/cobra" +) + +// versionCmd represents the version command +var uploadCmd = &cobra.Command{ + Use: "upload", + Short: "upload files to server", + RunE: func(cmd *cobra.Command, _ []string) error { + client, err := GetClient(cmd) + if err != nil { + return err + } + + path, err := cmd.Flags().GetString("path") + if err != nil { + return err + } + path = path2.Clean(path) + if len(path) == 0 { + return errors.New("path must be set") + } + + owner, err := cmd.Flags().GetString("owner") + if err != nil { + return err + } + if len(owner) == 0 { + return errors.New("owner must be set") + } + + repo, err := cmd.Flags().GetString("repo") + if err != nil { + return err + } + if len(owner) == 0 || len(repo) == 0 { + return errors.New("owner and repo must be set") + } + + refName, err := cmd.Flags().GetString("refName") + if err != nil { + return err + } + if len(refName) == 0 { + return errors.New("refName must be set") + } + + uploadPath, err := cmd.Flags().GetString("uploadPath") + if err != nil { + return err + } + uploadPath = path2.Clean(uploadPath) + if len(uploadPath) == 0 { + uploadPath = "/" + } + + if len(path) == 0 { + return errors.New("path not set") + } + + st, err := os.Stat(path) + if err != nil { + return err + } + + var files []string + if st.IsDir() { + err = filepath.Walk(path, func(path string, info fs.FileInfo, _ error) error { + if info.IsDir() { + return nil + } + files = append(files, path) + return nil + }) + if err != nil { + return err + } + } + + fmt.Printf("Files dected, %d files need to be uploaded\n", len(files)) + _, err = client.GetWip(cmd.Context(), owner, repo, &api.GetWipParams{RefName: refName}) + if err != nil { + return err + } + + basename := filepath.Base(path) + for _, file := range files { + fs, err := os.Open(file) + if err != nil { + return err + } + relativePath := strings.Replace(file, path, "", 1) + destPath := path2.Join(uploadPath, basename, relativePath) + + resp, err := client.UploadObjectWithBody(cmd.Context(), owner, repo, &api.UploadObjectParams{ + RefName: refName, + // Path relative to the ref + Path: destPath, + IsReplace: utils.Bool(true), + }, "application/json", fs) + if err != nil { + return err + } + + if resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusOK { + fmt.Println("Upload file success ", file, " dest path", destPath) + continue + } + return fmt.Errorf("upload file failed %d, %s", resp.StatusCode, tryLogError(resp)) + } + return nil + }, +} + +func init() { + rootCmd.AddCommand(uploadCmd) + + uploadCmd.Flags().String("path", "", "path of files to upload") + uploadCmd.Flags().String("owner", "", "owner") + uploadCmd.Flags().String("repo", "", "repo") + uploadCmd.Flags().String("refName", "main", "branch name") + uploadCmd.Flags().String("uploadPath", "", "path to save in server") + uploadCmd.Flags().Bool("replace", true, "path to save in server") +} diff --git a/cmd/version.go b/cmd/version.go index 72969e3f..299a33f0 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -21,7 +21,7 @@ var versionCmd = &cobra.Command{ fmt.Println("Version ", version.UserVersion()) fmt.Println("API Version ", swagger.Info.Version) - client, err := GetDefaultClient() + client, err := GetClient(cmd) if err != nil { return err } diff --git a/codecov.yml b/codecov.yml index 18ac0b51..fb492fe0 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,3 +1,4 @@ ignore: - "**/*.gen.go" - "examples" + - "cmd" diff --git a/controller/object_ctl.go b/controller/object_ctl.go index 8dd5f744..95668d05 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -1,6 +1,7 @@ package controller import ( + "bytes" "context" "errors" "fmt" @@ -388,7 +389,28 @@ func (oct ObjectController) UploadObject(ctx context.Context, w *api.JiaozifsRes path := versionmgr.CleanPath(params.Path) err = oct.Repo.Transaction(ctx, func(dRepo models.IRepo) error { - err = workTree.AddLeaf(ctx, path, blob) + oldData, _, err := workTree.FindBlob(ctx, path) + if err != nil && !errors.Is(err, versionmgr.ErrPathNotFound) { + return err + } + if oldData == nil { + err = workTree.AddLeaf(ctx, path, blob) + if err != nil { + return err + } + return dRepo.WipRepo().UpdateByID(ctx, models.NewUpdateWipParams(workRepo.CurWip().ID).SetCurrentTree(workTree.Root().Hash())) + } + + if bytes.Equal(oldData.CheckSum, blob.CheckSum) { + return nil + } + + if !utils.BoolValue(params.IsReplace) { + return fmt.Errorf("object exit %w", api.ErrCode(http.StatusConflict)) + } + + //allow to update + err = workTree.ReplaceLeaf(ctx, path, blob) if err != nil { return err } diff --git a/integrationtest/commit_test.go b/integrationtest/commit_test.go index a0925a90..9a4cb041 100644 --- a/integrationtest/commit_test.go +++ b/integrationtest/commit_test.go @@ -26,9 +26,9 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { _ = createRepo(ctx, client, repoName, false) _ = createBranch(ctx, client, userName, repoName, "main", branchName) _ = createWip(ctx, client, userName, repoName, branchName) - _ = uploadObject(ctx, client, userName, repoName, branchName, "m.dat") - _ = uploadObject(ctx, client, userName, repoName, branchName, "g/x.dat") - _ = uploadObject(ctx, client, userName, repoName, branchName, "g/m.dat") + _ = uploadObject(ctx, client, userName, repoName, branchName, "m.dat", true) + _ = uploadObject(ctx, client, userName, repoName, branchName, "g/x.dat", true) + _ = uploadObject(ctx, client, userName, repoName, branchName, "g/m.dat", true) }) c.Convey("get wip entries", func(c convey.C) { @@ -230,8 +230,8 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("prepare data for commit test", func(_ convey.C) { createWip(ctx, client, userName, repoName, "main") - uploadObject(ctx, client, userName, repoName, "main", "a.dat") //delete\ - uploadObject(ctx, client, userName, repoName, "main", "g/m.dat") //modify + uploadObject(ctx, client, userName, repoName, "main", "a.dat", true) //delete\ + uploadObject(ctx, client, userName, repoName, "main", "g/m.dat", true) //modify _ = commitWip(ctx, client, userName, repoName, "main", "test") }) @@ -389,10 +389,10 @@ func GetCommitChangesSpec(ctx context.Context, urlStr string) func(c convey.C) { loginAndSwitch(ctx, client, userName, false) createRepo(ctx, client, repoName, false) createWip(ctx, client, userName, repoName, "main") - uploadObject(ctx, client, userName, repoName, "main", "m.dat") + uploadObject(ctx, client, userName, repoName, "main", "m.dat", true) _ = commitWip(ctx, client, userName, repoName, "main", "test") - uploadObject(ctx, client, userName, repoName, "main", "g/x.dat") + uploadObject(ctx, client, userName, repoName, "main", "g/x.dat", true) _ = commitWip(ctx, client, userName, repoName, "main", "test") //delete @@ -400,10 +400,10 @@ func GetCommitChangesSpec(ctx context.Context, urlStr string) func(c convey.C) { //modify deleteObject(ctx, client, userName, repoName, "main", "m.dat") - uploadObject(ctx, client, userName, repoName, "main", "m.dat") + uploadObject(ctx, client, userName, repoName, "main", "m.dat", true) //insert - uploadObject(ctx, client, userName, repoName, "main", "g/m.dat") + uploadObject(ctx, client, userName, repoName, "main", "g/m.dat", true) _ = commitWip(ctx, client, userName, repoName, "main", "test") }) diff --git a/integrationtest/helper_test.go b/integrationtest/helper_test.go index b3135a75..7402ca0e 100644 --- a/integrationtest/helper_test.go +++ b/integrationtest/helper_test.go @@ -29,7 +29,7 @@ func InitCmd(ctx context.Context, jzHome string, listen string, db string) error buf := new(bytes.Buffer) cmd.RootCmd().SetOut(buf) cmd.RootCmd().SetErr(buf) - cmd.RootCmd().SetArgs([]string{"init", "--listen", listen, "--db_debug", "true", "--db", db, + cmd.RootCmd().SetArgs([]string{"init", "--listen", listen, "--db_debug", "false", "--db", db, "--config", fmt.Sprintf("%s/config.toml", jzHome), "--bs_path", fmt.Sprintf("%s/blockstore", jzHome)}) return cmd.RootCmd().ExecuteContext(ctx) @@ -171,11 +171,12 @@ func createRepo(ctx context.Context, client *api.Client, repoName string, visibl return result.JSON201 } -func uploadObject(ctx context.Context, client *api.Client, user string, repoName string, refName string, path string) *api.ObjectStats { //nolint +func uploadObject(ctx context.Context, client *api.Client, user string, repoName string, refName string, path string, replace bool) *api.ObjectStats { //nolint resp, err := client.UploadObjectWithBody(ctx, user, repoName, &api.UploadObjectParams{ - RefName: refName, - Path: path, - }, "application/octet-stream", io.LimitReader(rand.Reader, 50)) + RefName: refName, + Path: path, + IsReplace: utils.Bool(replace), + }, "application/octet-stream", io.LimitReader(rand.Reader, 100)) convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) diff --git a/integrationtest/merge_request_test.go b/integrationtest/merge_request_test.go index efdab4a0..3cf765c1 100644 --- a/integrationtest/merge_request_test.go +++ b/integrationtest/merge_request_test.go @@ -30,7 +30,7 @@ func MergeRequestSpec(ctx context.Context, urlStr string) func(c convey.C) { _ = createRepo(ctx, client, repoName, false) _ = createBranch(ctx, client, userName, repoName, "main", branchName) _ = createWip(ctx, client, userName, repoName, branchName) - _ = uploadObject(ctx, client, userName, repoName, branchName, "a.bin") + _ = uploadObject(ctx, client, userName, repoName, branchName, "a.bin", true) _ = commitWip(ctx, client, userName, repoName, branchName, "test") }) @@ -259,7 +259,7 @@ func MergeRequestSpec(ctx context.Context, urlStr string) func(c convey.C) { branchName := fmt.Sprintf("feat/list_merge_test_%d", i) createBranch(ctx, client, userName, repoName, "main", branchName) createWip(ctx, client, userName, repoName, branchName) - uploadObject(ctx, client, userName, repoName, branchName, fmt.Sprintf("%d.txt", i)) + uploadObject(ctx, client, userName, repoName, branchName, fmt.Sprintf("%d.txt", i), true) _ = commitWip(ctx, client, userName, repoName, branchName, "test") createMergeRequest(ctx, client, userName, repoName, branchName, "main") } diff --git a/integrationtest/objects_test.go b/integrationtest/objects_test.go index 4814093e..2e618e1e 100644 --- a/integrationtest/objects_test.go +++ b/integrationtest/objects_test.go @@ -123,6 +123,33 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(err, convey.ShouldBeNil) convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) }) + + c.Convey("success to upload the same resource", func() { + resp, err := client.UploadObjectWithBody(ctx, userName, repoName, &api.UploadObjectParams{ + RefName: branchName, + Path: "a/b.bin", + }, "application/octet-stream", bytes.NewReader([]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 1, 1, 1})) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + }) + + c.Convey("fail to upload difference content to exit path without tell serve to replace", func() { + resp, err := client.UploadObjectWithBody(ctx, userName, repoName, &api.UploadObjectParams{ + RefName: branchName, + Path: "a/b.bin", + }, "application/octet-stream", bytes.NewReader([]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 1, 1, 2})) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusConflict) + }) + c.Convey("success to upload difference content to exit path when tell serve to replace", func() { + resp, err := client.UploadObjectWithBody(ctx, userName, repoName, &api.UploadObjectParams{ + RefName: branchName, + Path: "a/b.bin", + IsReplace: utils.Bool(true), + }, "application/octet-stream", bytes.NewReader([]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 1, 1, 1})) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + }) }) //commit object to branch @@ -370,9 +397,9 @@ func ObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("list success", func() { _ = createWip(ctx, client, userName, repoName, branchName) - _ = uploadObject(ctx, client, userName, repoName, branchName, "a/b.txt") - _ = uploadObject(ctx, client, userName, repoName, branchName, "a/e.txt") - _ = uploadObject(ctx, client, userName, repoName, branchName, "a/g.txt") + _ = uploadObject(ctx, client, userName, repoName, branchName, "a/b.txt", true) + _ = uploadObject(ctx, client, userName, repoName, branchName, "a/e.txt", true) + _ = uploadObject(ctx, client, userName, repoName, branchName, "a/g.txt", true) _ = commitWip(ctx, client, userName, repoName, branchName, "wip") resp, err := client.GetFiles(ctx, userName, repoName, &api.GetFilesParams{ diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index bdb8a6fa..aa0d7cb7 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -405,7 +405,7 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { c.Convey("add commit to branch", func(_ convey.C) { createWip(ctx, client, userName, repoName, controller.DefaultBranchName) - uploadObject(ctx, client, userName, repoName, controller.DefaultBranchName, "a.txt") + uploadObject(ctx, client, userName, repoName, controller.DefaultBranchName, "a.txt", true) _ = commitWip(ctx, client, userName, repoName, controller.DefaultBranchName, "first commit") }) @@ -423,9 +423,9 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { }) c.Convey("add double commit to branch", func(_ convey.C) { - uploadObject(ctx, client, userName, repoName, controller.DefaultBranchName, "b.txt") + uploadObject(ctx, client, userName, repoName, controller.DefaultBranchName, "b.txt", true) _ = commitWip(ctx, client, userName, repoName, controller.DefaultBranchName, "second commit") - uploadObject(ctx, client, userName, repoName, controller.DefaultBranchName, "c.txt") + uploadObject(ctx, client, userName, repoName, controller.DefaultBranchName, "c.txt", true) _ = commitWip(ctx, client, userName, repoName, controller.DefaultBranchName, "third commit") }) diff --git a/integrationtest/wip_object_test.go b/integrationtest/wip_object_test.go index 430202ec..002c6bd6 100644 --- a/integrationtest/wip_object_test.go +++ b/integrationtest/wip_object_test.go @@ -25,8 +25,8 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { _ = createRepo(ctx, client, repoName, false) _ = createBranch(ctx, client, userName, repoName, "main", branchName) _ = createWip(ctx, client, userName, repoName, branchName) - _ = uploadObject(ctx, client, userName, repoName, branchName, "m.dat") - _ = uploadObject(ctx, client, userName, repoName, branchName, "g/m.dat") + _ = uploadObject(ctx, client, userName, repoName, branchName, "m.dat", true) + _ = uploadObject(ctx, client, userName, repoName, branchName, "g/m.dat", true) }) c.Convey("head object", func(c convey.C) { @@ -298,10 +298,10 @@ func WipObjectSpec(ctx context.Context, urlStr string) func(c convey.C) { testBranchName := "test/empty_branch" c.Convey("update objects and create empty branch", func(_ convey.C) { - uploadObject(ctx, client, userName, repoName, branchName, "a/m.dat") - uploadObject(ctx, client, userName, repoName, branchName, "a/b.dat") - uploadObject(ctx, client, userName, repoName, branchName, "b.dat") - uploadObject(ctx, client, userName, repoName, branchName, "c.dat") + uploadObject(ctx, client, userName, repoName, branchName, "a/m.dat", true) + uploadObject(ctx, client, userName, repoName, branchName, "a/b.dat", true) + uploadObject(ctx, client, userName, repoName, branchName, "b.dat", true) + uploadObject(ctx, client, userName, repoName, branchName, "c.dat", true) createBranch(ctx, client, userName, repoName, "main", testBranchName) createWip(ctx, client, userName, repoName, testBranchName) @@ -481,11 +481,11 @@ func UpdateWipSpec(ctx context.Context, urlStr string) func(c convey.C) { _ = createWip(ctx, client, userName, repoName, branchName) //make wip base commit has value - _ = uploadObject(ctx, client, userName, repoName, branchName, "a.txt") + _ = uploadObject(ctx, client, userName, repoName, branchName, "a.txt", true) _ = commitWip(ctx, client, userName, repoName, branchName, "test") - _ = uploadObject(ctx, client, userName, repoName, branchName, "m.dat") - _ = uploadObject(ctx, client, userName, repoName, branchName, "g/m.dat") + _ = uploadObject(ctx, client, userName, repoName, branchName, "m.dat", true) + _ = uploadObject(ctx, client, userName, repoName, branchName, "g/m.dat", true) }) c.Convey("get wip", func(_ convey.C) { From 00ed63022258975d8b49ed2677b531b46eeceff4 Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Sun, 17 Mar 2024 18:54:34 +0800 Subject: [PATCH 200/210] fix: support window seperate in get files api (#153) --- versionmgr/worktree.go | 1 + versionmgr/worktree_test.go | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/versionmgr/worktree.go b/versionmgr/worktree.go index af744213..9d9ede08 100644 --- a/versionmgr/worktree.go +++ b/versionmgr/worktree.go @@ -450,6 +450,7 @@ type TreeManifest struct { func (workTree *WorkTree) GetTreeManifest(ctx context.Context, pattern string) (TreeManifest, error) { //todo match all files, it maybe slow maybe need a new algo like filepath.Glob + pattern = strings.ReplaceAll(pattern, "\\", "/") wk := FileWalk{curNode: workTree.root, object: workTree.object} g, err := glob.Compile(pattern) if err != nil { diff --git a/versionmgr/worktree_test.go b/versionmgr/worktree_test.go index 776d1dfb..3097e6c1 100644 --- a/versionmgr/worktree_test.go +++ b/versionmgr/worktree_test.go @@ -189,6 +189,13 @@ func TestWorkTreeGetFiles(t *testing.T) { require.Equal(t, "ff/b/f.jpg", manifest.FileList[6]) }) + t.Run("replace windows style separate char", func(t *testing.T) { + manifest, err := workTree.GetTreeManifest(ctx, "a\\b\\d.txt") + require.NoError(t, err) + require.Equal(t, 1, len(manifest.FileList)) + require.Equal(t, "a/b/d.txt", manifest.FileList[0]) + }) + t.Run("single file", func(t *testing.T) { manifest, err := workTree.GetTreeManifest(ctx, "a/b/d.txt") require.NoError(t, err) From ecdf6e1a637564c213ba2a6f4e8659451749e70b Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Mon, 18 Mar 2024 10:52:34 +0800 Subject: [PATCH 201/210] fix: allow large file size (#156) --- api/jiaozifs.gen.go | 189 ++++++++++++++++++----------------- api/swagger.yml | 5 + chart/templates/ingress.yaml | 1 + chart/values.yaml | 1 + cmd/uploadfiles.go | 21 +++- controller/object_ctl.go | 1 + 6 files changed, 122 insertions(+), 96 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index a07ab113..423e5285 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -11430,100 +11430,101 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index f94c806c..8c025491 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -1124,6 +1124,11 @@ paths: type: string format: binary headers: + Content-Disposition: + schema: + type: string + description: response file + example: attachment; filename="name.pdf" Content-Length: schema: type: integer diff --git a/chart/templates/ingress.yaml b/chart/templates/ingress.yaml index 6f591858..5faf975b 100644 --- a/chart/templates/ingress.yaml +++ b/chart/templates/ingress.yaml @@ -6,6 +6,7 @@ metadata: meta.helm.sh/release-name: jiaozifs-api nginx.ingress.kubernetes.io/rewrite-target: / nginx.ingress.kubernetes.io/proxy-connect-timeout: "30" + nginx.ingress.kubernetes.io/proxy-body-size: "{{.Values.request_size}}" nginx.ingress.kubernetes.io/proxy-read-timeout: "1800" nginx.ingress.kubernetes.io/proxy-send-timeout: "1800" labels: diff --git a/chart/values.yaml b/chart/values.yaml index 3071e788..65c1a0f9 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -10,3 +10,4 @@ log_level: info claim_name: jiaozifs-home tag: latest cert: api-jiaozifs-com-tls +request_size: 100m diff --git a/cmd/uploadfiles.go b/cmd/uploadfiles.go index bf36b0b1..b7f20a62 100644 --- a/cmd/uploadfiles.go +++ b/cmd/uploadfiles.go @@ -69,6 +69,16 @@ var uploadCmd = &cobra.Command{ uploadPath = "/" } + ignoreRootName, err := cmd.Flags().GetBool("ignoreRootName") + if err != nil { + return err + } + + replace, err := cmd.Flags().GetBool("replace") + if err != nil { + return err + } + if len(path) == 0 { return errors.New("path not set") } @@ -105,13 +115,19 @@ var uploadCmd = &cobra.Command{ return err } relativePath := strings.Replace(file, path, "", 1) - destPath := path2.Join(uploadPath, basename, relativePath) + + var destPath string + if !ignoreRootName { + destPath = path2.Join(uploadPath, basename, relativePath) + } else { + destPath = path2.Join(uploadPath, relativePath) + } resp, err := client.UploadObjectWithBody(cmd.Context(), owner, repo, &api.UploadObjectParams{ RefName: refName, // Path relative to the ref Path: destPath, - IsReplace: utils.Bool(true), + IsReplace: utils.Bool(replace), }, "application/json", fs) if err != nil { return err @@ -136,4 +152,5 @@ func init() { uploadCmd.Flags().String("refName", "main", "branch name") uploadCmd.Flags().String("uploadPath", "", "path to save in server") uploadCmd.Flags().Bool("replace", true, "path to save in server") + uploadCmd.Flags().Bool("ignoreRootName", false, "ignore root name") } diff --git a/controller/object_ctl.go b/controller/object_ctl.go index 95668d05..9c911bd8 100644 --- a/controller/object_ctl.go +++ b/controller/object_ctl.go @@ -191,6 +191,7 @@ func (oct ObjectController) GetObject(ctx context.Context, w *api.JiaozifsRespon w.Header().Set("X-Content-Type-Options", "nosniff") w.Header().Set("X-Frame-Options", "SAMEORIGIN") w.Header().Set("Content-Security-Policy", "default-src 'none'") + w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, name)) _, err = io.Copy(w, reader) if err != nil { objLog.With( From 47bf304240ccfedcfc4e05324425e8deb75f56e9 Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Sat, 23 Mar 2024 20:32:18 +0800 Subject: [PATCH 202/210] feat: support tag (#159) --- api/api_impl/impl.go | 1 + api/jiaozifs.gen.go | 1337 +++++++++++++++++++++---- api/swagger.yml | 186 +++- auth/rbac/statements.go | 7 + controller/branch_ctl.go | 50 +- controller/commit_ctl.go | 13 + controller/repository_ctl.go | 2 +- controller/tag_ctl.go | 240 +++++ controller/validator/validate.go | 11 +- controller/validator/validate_test.go | 28 + integrationtest/branch_test.go | 1 - integrationtest/commit_test.go | 23 + integrationtest/helper_test.go | 24 + integrationtest/root_test.go | 1 + integrationtest/tag_test.go | 331 ++++++ models/rbacmodel/actions.gen.go | 7 +- models/rbacmodel/actions.go | 8 +- models/repo.go | 6 +- models/tag.go | 175 +++- models/tag_test.go | 125 ++- versionmgr/work_repo.go | 95 +- versionmgr/work_repo_test.go | 45 +- 22 files changed, 2378 insertions(+), 338 deletions(-) create mode 100644 controller/tag_ctl.go create mode 100644 integrationtest/tag_test.go diff --git a/api/api_impl/impl.go b/api/api_impl/impl.go index 46d00440..078570e6 100644 --- a/api/api_impl/impl.go +++ b/api/api_impl/impl.go @@ -23,4 +23,5 @@ type APIController struct { controller.GroupController controller.MemberController + controller.TagController } diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 423e5285..2f1af506 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -349,6 +349,33 @@ type Signature struct { When int64 `json:"when"` } +// Tag defines model for Tag. +type Tag struct { + CreatedAt int64 `json:"created_at"` + CreatorId openapi_types.UUID `json:"creator_id"` + Id openapi_types.UUID `json:"id"` + Message *string `json:"message,omitempty"` + Name string `json:"name"` + RepositoryId openapi_types.UUID `json:"repository_id"` + Target string `json:"target"` + UpdatedAt int64 `json:"updated_at"` +} + +// TagCreation defines model for TagCreation. +type TagCreation struct { + Message *string `json:"message,omitempty"` + Name string `json:"name"` + + // Target target branch name or commit hex, first try branch and then commit + Target string `json:"target"` +} + +// TagList defines model for TagList. +type TagList struct { + Pagination Pagination `json:"pagination"` + Results []Tag `json:"results"` +} + // UpdateMergeRequest defines model for UpdateMergeRequest. type UpdateMergeRequest struct { Description *string `json:"description,omitempty"` @@ -600,6 +627,28 @@ type ListMergeRequestsParams struct { State *int `form:"state,omitempty" json:"state,omitempty"` } +// DeleteTagParams defines parameters for DeleteTag. +type DeleteTagParams struct { + RefName string `form:"refName" json:"refName"` +} + +// GetTagParams defines parameters for GetTag. +type GetTagParams struct { + RefName string `form:"refName" json:"refName"` +} + +// ListTagsParams defines parameters for ListTags. +type ListTagsParams struct { + // Prefix return items prefixed with this value + Prefix *PaginationPrefix `form:"prefix,omitempty" json:"prefix,omitempty"` + + // After return items after this value + After *PaginationInt64After `form:"after,omitempty" json:"after,omitempty"` + + // Amount how many items to return + Amount *PaginationAmount `form:"amount,omitempty" json:"amount,omitempty"` +} + // ChangeVisibleParams defines parameters for ChangeVisible. type ChangeVisibleParams struct { Visible bool `form:"visible" json:"visible"` @@ -721,6 +770,9 @@ type UpdateMergeRequestJSONRequestBody = UpdateMergeRequest // MergeJSONRequestBody defines body for Merge for application/json ContentType. type MergeJSONRequestBody = MergeMergeRequest +// CreateTagJSONRequestBody defines body for CreateTag for application/json ContentType. +type CreateTagJSONRequestBody = TagCreation + // RegisterJSONRequestBody defines body for Register for application/json ContentType. type RegisterJSONRequestBody = UserRegisterInfo @@ -902,6 +954,20 @@ type ClientInterface interface { Merge(ctx context.Context, owner string, repository string, mrSeq uint64, body MergeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // DeleteTag request + DeleteTag(ctx context.Context, owner string, repository string, params *DeleteTagParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetTag request + GetTag(ctx context.Context, owner string, repository string, params *GetTagParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateTagWithBody request with any body + CreateTagWithBody(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateTag(ctx context.Context, owner string, repository string, body CreateTagJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // ListTags request + ListTags(ctx context.Context, owner string, repository string, params *ListTagsParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // ChangeVisible request ChangeVisible(ctx context.Context, owner string, repository string, params *ChangeVisibleParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -1389,6 +1455,66 @@ func (c *Client) Merge(ctx context.Context, owner string, repository string, mrS return c.Client.Do(req) } +func (c *Client) DeleteTag(ctx context.Context, owner string, repository string, params *DeleteTagParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteTagRequest(c.Server, owner, repository, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetTag(ctx context.Context, owner string, repository string, params *GetTagParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetTagRequest(c.Server, owner, repository, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateTagWithBody(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateTagRequestWithBody(c.Server, owner, repository, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateTag(ctx context.Context, owner string, repository string, body CreateTagJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateTagRequest(c.Server, owner, repository, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) ListTags(ctx context.Context, owner string, repository string, params *ListTagsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListTagsRequest(c.Server, owner, repository, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) ChangeVisible(ctx context.Context, owner string, repository string, params *ChangeVisibleParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewChangeVisibleRequest(c.Server, owner, repository, params) if err != nil { @@ -3595,8 +3721,8 @@ func NewMergeRequestWithBody(server string, owner string, repository string, mrS return req, nil } -// NewChangeVisibleRequest generates requests for ChangeVisible -func NewChangeVisibleRequest(server string, owner string, repository string, params *ChangeVisibleParams) (*http.Request, error) { +// NewDeleteTagRequest generates requests for DeleteTag +func NewDeleteTagRequest(server string, owner string, repository string, params *DeleteTagParams) (*http.Request, error) { var err error var pathParam0 string @@ -3618,7 +3744,7 @@ func NewChangeVisibleRequest(server string, owner string, repository string, par return nil, err } - operationPath := fmt.Sprintf("/repos/%s/%s/visible", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/repos/%s/%s/tag", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -3631,7 +3757,7 @@ func NewChangeVisibleRequest(server string, owner string, repository string, par if params != nil { queryValues := queryURL.Query() - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "visible", runtime.ParamLocationQuery, params.Visible); err != nil { + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -3646,7 +3772,7 @@ func NewChangeVisibleRequest(server string, owner string, repository string, par queryURL.RawQuery = queryValues.Encode() } - req, err := http.NewRequest("POST", queryURL.String(), nil) + req, err := http.NewRequest("DELETE", queryURL.String(), nil) if err != nil { return nil, err } @@ -3654,16 +3780,30 @@ func NewChangeVisibleRequest(server string, owner string, repository string, par return req, nil } -// NewGetSetupStateRequest generates requests for GetSetupState -func NewGetSetupStateRequest(server string) (*http.Request, error) { +// NewGetTagRequest generates requests for GetTag +func NewGetTagRequest(server string, owner string, repository string, params *GetTagParams) (*http.Request, error) { var err error + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/setup") + operationPath := fmt.Sprintf("/repos/%s/%s/tag", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -3673,6 +3813,24 @@ func NewGetSetupStateRequest(server string) (*http.Request, error) { return nil, err } + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err @@ -3681,16 +3839,41 @@ func NewGetSetupStateRequest(server string) (*http.Request, error) { return req, nil } -// NewDeleteAkskRequest generates requests for DeleteAksk -func NewDeleteAkskRequest(server string, params *DeleteAkskParams) (*http.Request, error) { +// NewCreateTagRequest calls the generic CreateTag builder with application/json body +func NewCreateTagRequest(server string, owner string, repository string, body CreateTagJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateTagRequestWithBody(server, owner, repository, "application/json", bodyReader) +} + +// NewCreateTagRequestWithBody generates requests for CreateTag with any type of body +func NewCreateTagRequestWithBody(server string, owner string, repository string, contentType string, body io.Reader) (*http.Request, error) { var err error + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/users/aksk") + operationPath := fmt.Sprintf("/repos/%s/%s/tag", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -3700,62 +3883,40 @@ func NewDeleteAkskRequest(server string, params *DeleteAkskParams) (*http.Reques return nil, err } - if params != nil { - queryValues := queryURL.Query() - - if params.Id != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "id", runtime.ParamLocationQuery, *params.Id); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } - if params.AccessKey != nil { + req.Header.Add("Content-Type", contentType) - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "access_key", runtime.ParamLocationQuery, *params.AccessKey); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } + return req, nil +} - } +// NewListTagsRequest generates requests for ListTags +func NewListTagsRequest(server string, owner string, repository string, params *ListTagsParams) (*http.Request, error) { + var err error - queryURL.RawQuery = queryValues.Encode() - } + var pathParam0 string - req, err := http.NewRequest("DELETE", queryURL.String(), nil) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) if err != nil { return nil, err } - return req, nil -} + var pathParam1 string -// NewGetAkskRequest generates requests for GetAksk -func NewGetAkskRequest(server string, params *GetAkskParams) (*http.Request, error) { - var err error + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/users/aksk") + operationPath := fmt.Sprintf("/repos/%s/%s/tags", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -3768,9 +3929,9 @@ func NewGetAkskRequest(server string, params *GetAkskParams) (*http.Request, err if params != nil { queryValues := queryURL.Query() - if params.Id != nil { + if params.Prefix != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "id", runtime.ParamLocationQuery, *params.Id); err != nil { + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "prefix", runtime.ParamLocationQuery, *params.Prefix); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -3784,9 +3945,25 @@ func NewGetAkskRequest(server string, params *GetAkskParams) (*http.Request, err } - if params.AccessKey != nil { + if params.After != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "access_key", runtime.ParamLocationQuery, *params.AccessKey); err != nil { + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "after", runtime.ParamLocationQuery, *params.After); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Amount != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "amount", runtime.ParamLocationQuery, *params.Amount); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -3811,16 +3988,30 @@ func NewGetAkskRequest(server string, params *GetAkskParams) (*http.Request, err return req, nil } -// NewCreateAkskRequest generates requests for CreateAksk -func NewCreateAkskRequest(server string, params *CreateAkskParams) (*http.Request, error) { +// NewChangeVisibleRequest generates requests for ChangeVisible +func NewChangeVisibleRequest(server string, owner string, repository string, params *ChangeVisibleParams) (*http.Request, error) { var err error + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/users/aksk") + operationPath := fmt.Sprintf("/repos/%s/%s/visible", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -3833,10 +4024,212 @@ func NewCreateAkskRequest(server string, params *CreateAkskParams) (*http.Reques if params != nil { queryValues := queryURL.Query() - if params.Description != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "description", runtime.ParamLocationQuery, *params.Description); err != nil { - return nil, err + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "visible", runtime.ParamLocationQuery, params.Visible); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("POST", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetSetupStateRequest generates requests for GetSetupState +func NewGetSetupStateRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/setup") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewDeleteAkskRequest generates requests for DeleteAksk +func NewDeleteAkskRequest(server string, params *DeleteAkskParams) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/users/aksk") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Id != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "id", runtime.ParamLocationQuery, *params.Id); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.AccessKey != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "access_key", runtime.ParamLocationQuery, *params.AccessKey); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetAkskRequest generates requests for GetAksk +func NewGetAkskRequest(server string, params *GetAkskParams) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/users/aksk") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Id != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "id", runtime.ParamLocationQuery, *params.Id); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.AccessKey != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "access_key", runtime.ParamLocationQuery, *params.AccessKey); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewCreateAkskRequest generates requests for CreateAksk +func NewCreateAkskRequest(server string, params *CreateAkskParams) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/users/aksk") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Description != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "description", runtime.ParamLocationQuery, *params.Description); err != nil { + return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err } else { @@ -4849,6 +5242,20 @@ type ClientWithResponsesInterface interface { MergeWithResponse(ctx context.Context, owner string, repository string, mrSeq uint64, body MergeJSONRequestBody, reqEditors ...RequestEditorFn) (*MergeResponse, error) + // DeleteTagWithResponse request + DeleteTagWithResponse(ctx context.Context, owner string, repository string, params *DeleteTagParams, reqEditors ...RequestEditorFn) (*DeleteTagResponse, error) + + // GetTagWithResponse request + GetTagWithResponse(ctx context.Context, owner string, repository string, params *GetTagParams, reqEditors ...RequestEditorFn) (*GetTagResponse, error) + + // CreateTagWithBodyWithResponse request with any body + CreateTagWithBodyWithResponse(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateTagResponse, error) + + CreateTagWithResponse(ctx context.Context, owner string, repository string, body CreateTagJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateTagResponse, error) + + // ListTagsWithResponse request + ListTagsWithResponse(ctx context.Context, owner string, repository string, params *ListTagsParams, reqEditors ...RequestEditorFn) (*ListTagsResponse, error) + // ChangeVisibleWithResponse request ChangeVisibleWithResponse(ctx context.Context, owner string, repository string, params *ChangeVisibleParams, reqEditors ...RequestEditorFn) (*ChangeVisibleResponse, error) @@ -5543,13 +5950,13 @@ func (r MergeResponse) StatusCode() int { return 0 } -type ChangeVisibleResponse struct { +type DeleteTagResponse struct { Body []byte HTTPResponse *http.Response } // Status returns HTTPResponse.Status -func (r ChangeVisibleResponse) Status() string { +func (r DeleteTagResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -5557,21 +5964,21 @@ func (r ChangeVisibleResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r ChangeVisibleResponse) StatusCode() int { +func (r DeleteTagResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetSetupStateResponse struct { +type GetTagResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *SetupState + JSON200 *Tag } // Status returns HTTPResponse.Status -func (r GetSetupStateResponse) Status() string { +func (r GetTagResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -5579,20 +5986,21 @@ func (r GetSetupStateResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetSetupStateResponse) StatusCode() int { +func (r GetTagResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type DeleteAkskResponse struct { +type CreateTagResponse struct { Body []byte HTTPResponse *http.Response + JSON201 *Tag } // Status returns HTTPResponse.Status -func (r DeleteAkskResponse) Status() string { +func (r CreateTagResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -5600,21 +6008,21 @@ func (r DeleteAkskResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r DeleteAkskResponse) StatusCode() int { +func (r CreateTagResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetAkskResponse struct { +type ListTagsResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *SafeAksk + JSON200 *TagList } // Status returns HTTPResponse.Status -func (r GetAkskResponse) Status() string { +func (r ListTagsResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -5622,21 +6030,20 @@ func (r GetAkskResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetAkskResponse) StatusCode() int { +func (r ListTagsResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type CreateAkskResponse struct { +type ChangeVisibleResponse struct { Body []byte HTTPResponse *http.Response - JSON201 *Aksk } // Status returns HTTPResponse.Status -func (r CreateAkskResponse) Status() string { +func (r ChangeVisibleResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -5644,21 +6051,21 @@ func (r CreateAkskResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r CreateAkskResponse) StatusCode() int { +func (r ChangeVisibleResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type ListAksksResponse struct { +type GetSetupStateResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *AkskList + JSON200 *SetupState } // Status returns HTTPResponse.Status -func (r ListAksksResponse) Status() string { +func (r GetSetupStateResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -5666,21 +6073,20 @@ func (r ListAksksResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r ListAksksResponse) StatusCode() int { +func (r GetSetupStateResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type RefreshTokenResponse struct { +type DeleteAkskResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *AuthenticationToken } // Status returns HTTPResponse.Status -func (r RefreshTokenResponse) Status() string { +func (r DeleteAkskResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -5688,21 +6094,21 @@ func (r RefreshTokenResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r RefreshTokenResponse) StatusCode() int { +func (r DeleteAkskResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type RegisterResponse struct { +type GetAkskResponse struct { Body []byte HTTPResponse *http.Response - JSON201 *UserInfo + JSON200 *SafeAksk } // Status returns HTTPResponse.Status -func (r RegisterResponse) Status() string { +func (r GetAkskResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -5710,14 +6116,102 @@ func (r RegisterResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r RegisterResponse) StatusCode() int { +func (r GetAkskResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type ListRepositoryOfAuthenticatedUserResponse struct { +type CreateAkskResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *Aksk +} + +// Status returns HTTPResponse.Status +func (r CreateAkskResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateAkskResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type ListAksksResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *AkskList +} + +// Status returns HTTPResponse.Status +func (r ListAksksResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ListAksksResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type RefreshTokenResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *AuthenticationToken +} + +// Status returns HTTPResponse.Status +func (r RefreshTokenResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r RefreshTokenResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type RegisterResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *UserInfo +} + +// Status returns HTTPResponse.Status +func (r RegisterResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r RegisterResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type ListRepositoryOfAuthenticatedUserResponse struct { Body []byte HTTPResponse *http.Response JSON200 *RepositoryList @@ -6288,6 +6782,50 @@ func (c *ClientWithResponses) MergeWithResponse(ctx context.Context, owner strin return ParseMergeResponse(rsp) } +// DeleteTagWithResponse request returning *DeleteTagResponse +func (c *ClientWithResponses) DeleteTagWithResponse(ctx context.Context, owner string, repository string, params *DeleteTagParams, reqEditors ...RequestEditorFn) (*DeleteTagResponse, error) { + rsp, err := c.DeleteTag(ctx, owner, repository, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteTagResponse(rsp) +} + +// GetTagWithResponse request returning *GetTagResponse +func (c *ClientWithResponses) GetTagWithResponse(ctx context.Context, owner string, repository string, params *GetTagParams, reqEditors ...RequestEditorFn) (*GetTagResponse, error) { + rsp, err := c.GetTag(ctx, owner, repository, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetTagResponse(rsp) +} + +// CreateTagWithBodyWithResponse request with arbitrary body returning *CreateTagResponse +func (c *ClientWithResponses) CreateTagWithBodyWithResponse(ctx context.Context, owner string, repository string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateTagResponse, error) { + rsp, err := c.CreateTagWithBody(ctx, owner, repository, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateTagResponse(rsp) +} + +func (c *ClientWithResponses) CreateTagWithResponse(ctx context.Context, owner string, repository string, body CreateTagJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateTagResponse, error) { + rsp, err := c.CreateTag(ctx, owner, repository, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateTagResponse(rsp) +} + +// ListTagsWithResponse request returning *ListTagsResponse +func (c *ClientWithResponses) ListTagsWithResponse(ctx context.Context, owner string, repository string, params *ListTagsParams, reqEditors ...RequestEditorFn) (*ListTagsResponse, error) { + rsp, err := c.ListTags(ctx, owner, repository, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseListTagsResponse(rsp) +} + // ChangeVisibleWithResponse request returning *ChangeVisibleResponse func (c *ClientWithResponses) ChangeVisibleWithResponse(ctx context.Context, owner string, repository string, params *ChangeVisibleParams, reqEditors ...RequestEditorFn) (*ChangeVisibleResponse, error) { rsp, err := c.ChangeVisible(ctx, owner, repository, params, reqEditors...) @@ -7136,6 +7674,100 @@ func ParseMergeResponse(rsp *http.Response) (*MergeResponse, error) { return response, nil } +// ParseDeleteTagResponse parses an HTTP response from a DeleteTagWithResponse call +func ParseDeleteTagResponse(rsp *http.Response) (*DeleteTagResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DeleteTagResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetTagResponse parses an HTTP response from a GetTagWithResponse call +func ParseGetTagResponse(rsp *http.Response) (*GetTagResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetTagResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Tag + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseCreateTagResponse parses an HTTP response from a CreateTagWithResponse call +func ParseCreateTagResponse(rsp *http.Response) (*CreateTagResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateTagResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest Tag + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil +} + +// ParseListTagsResponse parses an HTTP response from a ListTagsWithResponse call +func ParseListTagsResponse(rsp *http.Response) (*ListTagsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListTagsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest TagList + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseChangeVisibleResponse parses an HTTP response from a ChangeVisibleWithResponse call func ParseChangeVisibleResponse(rsp *http.Response) (*ChangeVisibleResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -7702,6 +8334,18 @@ type ServerInterface interface { // merge a mergerequest // (POST /repos/{owner}/{repository}/mergerequest/{mrSeq}/merge) Merge(ctx context.Context, w *JiaozifsResponse, r *http.Request, body MergeJSONRequestBody, owner string, repository string, mrSeq uint64) + // delete tag + // (DELETE /repos/{owner}/{repository}/tag) + DeleteTag(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteTagParams) + // get tag + // (GET /repos/{owner}/{repository}/tag) + GetTag(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetTagParams) + // create tag + // (POST /repos/{owner}/{repository}/tag) + CreateTag(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateTagJSONRequestBody, owner string, repository string) + // list tags + // (GET /repos/{owner}/{repository}/tags) + ListTags(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params ListTagsParams) // change repository visible(true for public, false for private) // (POST /repos/{owner}/{repository}/visible) ChangeVisible(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params ChangeVisibleParams) @@ -7941,6 +8585,30 @@ func (_ Unimplemented) Merge(ctx context.Context, w *JiaozifsResponse, r *http.R w.WriteHeader(http.StatusNotImplemented) } +// delete tag +// (DELETE /repos/{owner}/{repository}/tag) +func (_ Unimplemented) DeleteTag(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteTagParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get tag +// (GET /repos/{owner}/{repository}/tag) +func (_ Unimplemented) GetTag(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetTagParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// create tag +// (POST /repos/{owner}/{repository}/tag) +func (_ Unimplemented) CreateTag(ctx context.Context, w *JiaozifsResponse, r *http.Request, body CreateTagJSONRequestBody, owner string, repository string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// list tags +// (GET /repos/{owner}/{repository}/tags) +func (_ Unimplemented) ListTags(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params ListTagsParams) { + w.WriteHeader(http.StatusNotImplemented) +} + // change repository visible(true for public, false for private) // (POST /repos/{owner}/{repository}/visible) func (_ Unimplemented) ChangeVisible(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params ChangeVisibleParams) { @@ -10108,6 +10776,283 @@ func (siw *ServerInterfaceWrapper) Merge(w http.ResponseWriter, r *http.Request) handler.ServeHTTP(w, r.WithContext(ctx)) } +// DeleteTag operation middleware +func (siw *ServerInterfaceWrapper) DeleteTag(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params DeleteTagParams + + // ------------- Required query parameter "refName" ------------- + + if paramValue := r.URL.Query().Get("refName"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteTag(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// GetTag operation middleware +func (siw *ServerInterfaceWrapper) GetTag(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetTagParams + + // ------------- Required query parameter "refName" ------------- + + if paramValue := r.URL.Query().Get("refName"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetTag(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// CreateTag operation middleware +func (siw *ServerInterfaceWrapper) CreateTag(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Body parse ------------- + var body CreateTagJSONRequestBody + parseBody := true + if parseBody { + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "Error unmarshalling body 'CreateTag' as JSON", http.StatusBadRequest) + return + } + } + + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CreateTag(r.Context(), &JiaozifsResponse{w}, r, body, owner, repository) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// ListTags operation middleware +func (siw *ServerInterfaceWrapper) ListTags(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params ListTagsParams + + // ------------- Optional query parameter "prefix" ------------- + + err = runtime.BindQueryParameter("form", true, false, "prefix", r.URL.Query(), ¶ms.Prefix) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "prefix", Err: err}) + return + } + + // ------------- Optional query parameter "after" ------------- + + err = runtime.BindQueryParameter("form", true, false, "after", r.URL.Query(), ¶ms.After) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "after", Err: err}) + return + } + + // ------------- Optional query parameter "amount" ------------- + + err = runtime.BindQueryParameter("form", true, false, "amount", r.URL.Query(), ¶ms.Amount) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "amount", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListTags(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // ChangeVisible operation middleware func (siw *ServerInterfaceWrapper) ChangeVisible(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -11363,6 +12308,18 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/repos/{owner}/{repository}/mergerequest/{mrSeq}/merge", wrapper.Merge) }) + r.Group(func(r chi.Router) { + r.Delete(options.BaseURL+"/repos/{owner}/{repository}/tag", wrapper.DeleteTag) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/repos/{owner}/{repository}/tag", wrapper.GetTag) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/repos/{owner}/{repository}/tag", wrapper.CreateTag) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/repos/{owner}/{repository}/tags", wrapper.ListTags) + }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/repos/{owner}/{repository}/visible", wrapper.ChangeVisible) }) @@ -11430,101 +12387,105 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "vbz9vwAAAP//4udHdMq7AAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 8c025491..58ee746a 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -422,6 +422,63 @@ components: type: array items: $ref: "#/components/schemas/MergeRequest" + TagList: + type: object + required: + - pagination + - results + properties: + pagination: + $ref: "#/components/schemas/Pagination" + results: + type: array + items: + $ref: "#/components/schemas/Tag" + TagCreation: + type: object + required: + - name + - target + properties: + name: + type: string + target: + type: string + description: target branch name or commit hex, first try branch and then commit + message: + type: string + Tag: + type: object + required: + - id + - repository_id + - name + - creator_id + - target + - created_at + - updated_at + properties: + id : + type: string + format: uuid + repository_id: + type: string + format: uuid + name: + type: string + creator_id: + type: string + format: uuid + target: + type: string + message: + type: string + created_at: + type: integer + format: int64 + updated_at: + type: integer + format: int64 Branch: type: object required: @@ -2395,7 +2452,7 @@ paths: schema: type: string responses: - 204: + 200: description: branch delete successfully 401: description: Unauthorized @@ -2434,6 +2491,133 @@ paths: default: description: Internal Server Error + + /repos/{owner}/{repository}/tags: + parameters: + - in: path + name: owner + required: true + schema: + type: string + - in: path + name: repository + required: true + schema: + type: string + get: + tags: + - tags + operationId: listTags + summary: list tags + parameters: + - $ref: "#/components/parameters/PaginationPrefix" + - $ref: "#/components/parameters/PaginationInt64After" + - $ref: "#/components/parameters/PaginationAmount" + responses: + 200: + description: tag list + content: + application/json: + schema: + $ref: "#/components/schemas/TagList" + 401: + description: Unauthorized + 404: + description: Resource Not Found + 420: + description: Too many requests + default: + description: Internal Server Error + + /repos/{owner}/{repository}/tag: + parameters: + - in: path + name: owner + required: true + schema: + type: string + - in: path + name: repository + required: true + schema: + type: string + get: + tags: + - tags + operationId: getTag + summary: get tag + parameters: + - in: query + name: refName + required: true + schema: + type: string + responses: + 200: + description: tag + content: + application/json: + schema: + $ref: "#/components/schemas/Tag" + 401: + description: Unauthorized + 404: + description: Resource Not Found + 420: + description: Too many requests + default: + description: Internal Server Error + delete: + tags: + - tags + operationId: deleteTag + summary: delete tag + parameters: + - in: query + name: refName + required: true + schema: + type: string + responses: + 200: + description: tag delete successfully + 401: + description: Unauthorized + 404: + description: Resource Not Found + 420: + description: Too many requests + default: + description: Internal Server Error + post: + tags: + - tags + operationId: createTag + summary: create tag + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/TagCreation" + responses: + 201: + description: create tag success + content: + application/json: + schema: + $ref: "#/components/schemas/Tag" + 400: + description: ValidationError + 404: + description: Resource Not Found + 409: + description: Resource Conflicts With Target + 420: + description: Too many requests + default: + description: Internal Server Error + /groups/repo: get: tags: diff --git a/auth/rbac/statements.go b/auth/rbac/statements.go index d5d4125a..29cd219d 100644 --- a/auth/rbac/statements.go +++ b/auth/rbac/statements.go @@ -39,6 +39,13 @@ var statementByName = map[string]rbacmodel.Statement{ rbacmodel.ReadBranchAction, rbacmodel.ListBranchesAction, rbacmodel.WriteBranchAction, + + rbacmodel.CreateTagAction, + rbacmodel.DeleteTagAction, + rbacmodel.ReadTagAction, + rbacmodel.ListTagsAction, + rbacmodel.WriteTagAction, + rbacmodel.DeleteWipAction, rbacmodel.CreateMergeRequestAction, diff --git a/controller/branch_ctl.go b/controller/branch_ctl.go index e25a44ac..5d91c00e 100644 --- a/controller/branch_ctl.go +++ b/controller/branch_ctl.go @@ -67,20 +67,7 @@ func (bct BranchController) ListBranches(ctx context.Context, w *api.JiaozifsRes w.Error(err) return } - results := make([]api.Branch, 0, len(branches)) - for _, branch := range branches { - r := api.Branch{ - CommitHash: branch.CommitHash.Hex(), - CreatedAt: branch.CreatedAt.UnixMilli(), - CreatorId: branch.CreatorID, - Description: branch.Description, - Id: branch.ID, - Name: branch.Name, - RepositoryId: branch.RepositoryID, - UpdatedAt: branch.UpdatedAt.UnixMilli(), - } - results = append(results, r) - } + results := utils.Silent(utils.ArrMap(branches, branchToDto)) pagMag := utils.PaginationFor(hasMore, results, "Name") pagination := api.Pagination{ HasMore: pagMag.HasMore, @@ -153,16 +140,7 @@ func (bct BranchController) CreateBranch(ctx context.Context, w *api.JiaozifsRes return } - w.JSON(api.Branch{ - CommitHash: newBranch.CommitHash.Hex(), - CreatedAt: newBranch.CreatedAt.UnixMilli(), - CreatorId: newBranch.CreatorID, - Description: newBranch.Description, - Id: newBranch.ID, - Name: newBranch.Name, - RepositoryId: newBranch.RepositoryID, - UpdatedAt: newBranch.UpdatedAt.UnixMilli(), - }, http.StatusCreated) + w.JSON(utils.Silent(branchToDto(newBranch)), http.StatusCreated) } func (bct BranchController) DeleteBranch(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.DeleteBranchParams) { @@ -248,14 +226,18 @@ func (bct BranchController) GetBranch(ctx context.Context, w *api.JiaozifsRespon w.Error(err) return } - w.JSON(api.Branch{ - CommitHash: ref.CommitHash.Hex(), - CreatedAt: ref.CreatedAt.UnixMilli(), - CreatorId: ref.CreatorID, - Description: ref.Description, - Id: ref.ID, - Name: ref.Name, - RepositoryId: ref.RepositoryID, - UpdatedAt: ref.UpdatedAt.UnixMilli(), - }) + w.JSON(utils.Silent(branchToDto(ref))) +} + +func branchToDto(in *models.Branch) (api.Branch, error) { + return api.Branch{ + CommitHash: in.CommitHash.Hex(), + CreatedAt: in.CreatedAt.UnixMilli(), + CreatorId: in.CreatorID, + Description: in.Description, + Id: in.ID, + Name: in.Name, + RepositoryId: in.RepositoryID, + UpdatedAt: in.UpdatedAt.UnixMilli(), + }, nil } diff --git a/controller/commit_ctl.go b/controller/commit_ctl.go index 3d990ffc..9cd4056e 100644 --- a/controller/commit_ctl.go +++ b/controller/commit_ctl.go @@ -95,6 +95,19 @@ func (commitCtl CommitController) GetEntriesInRef(ctx context.Context, w *api.Ji } treeHash = commit.TreeHash } + } else if params.Type == api.RefTypeTag { + refName := utils.StringValue(params.Ref) + ref, err := commitCtl.Repo.TagRepo().Get(ctx, models.NewGetTagParams().SetRepositoryID(repository.ID).SetName(refName)) + if err != nil { + w.Error(err) + return + } + commit, err := commitCtl.Repo.CommitRepo(repository.ID).Commit(ctx, ref.Target) + if err != nil { + w.Error(err) + return + } + treeHash = commit.TreeHash } else if params.Type == api.RefTypeCommit { commitHash, err := hash.FromHex(utils.StringValue(params.Ref)) if err != nil { diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index 70885f4a..bde13f83 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -313,7 +313,7 @@ func (repositoryCtl RepositoryController) DeleteRepository(ctx context.Context, } //delete tag - _, err = repo.TagRepo(repository.ID).Delete(ctx, models.NewDeleteParams()) + _, err = repo.TagRepo().Delete(ctx, models.NewDeleteTagParams().SetRepositoryID(repository.ID)) if err != nil { return err } diff --git a/controller/tag_ctl.go b/controller/tag_ctl.go new file mode 100644 index 00000000..ade768e2 --- /dev/null +++ b/controller/tag_ctl.go @@ -0,0 +1,240 @@ +package controller + +import ( + "context" + "errors" + "net/http" + "time" + + "github.com/GitDataAI/jiaozifs/auth" + "github.com/GitDataAI/jiaozifs/auth/rbac" + "github.com/GitDataAI/jiaozifs/controller/validator" + "github.com/GitDataAI/jiaozifs/models/rbacmodel" + "github.com/GitDataAI/jiaozifs/utils" + "github.com/GitDataAI/jiaozifs/versionmgr" + + "github.com/GitDataAI/jiaozifs/api" + "github.com/GitDataAI/jiaozifs/block/params" + "github.com/GitDataAI/jiaozifs/models" + "go.uber.org/fx" +) + +type TagController struct { + fx.In + BaseController + + Repo models.IRepo + PublicStorageConfig params.AdapterConfig +} + +func (tagCtl TagController) CreateTag(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, body api.CreateTagJSONRequestBody, ownerName string, repositoryName string) { + if err := validator.ValidateTagName(body.Name); err != nil { + w.BadRequest(err.Error()) + return + } + + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return + } + + owner, err := tagCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + // Get repo + repository, err := tagCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) + if err != nil { + w.Error(err) + return + } + + if !tagCtl.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.CreateTagAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { + return + } + + workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repository, tagCtl.Repo, tagCtl.PublicStorageConfig) + if err != nil { + w.Error(err) + return + } + + //check target is branch + _, err = tagCtl.Repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.ID).SetName(body.Target)) + if err == nil { + // branch + err = workRepo.CheckOut(ctx, versionmgr.InBranch, body.Target) + } else if errors.Is(err, models.ErrNotFound) { + // commit + err = workRepo.CheckOut(ctx, versionmgr.InCommit, body.Target) + } + if err != nil { + w.Error(err) + return + } + + newTag, err := workRepo.CreateTag(ctx, body.Name, body.Message) + if err != nil { + w.Error(err) + return + } + + w.JSON(utils.Silent(tagToDto(newTag)), http.StatusCreated) +} + +func (tagCtl TagController) DeleteTag(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.DeleteTagParams) { + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return + } + + owner, err := tagCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + // Get repo + repository, err := tagCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) + if err != nil { + w.Error(err) + return + } + + if !tagCtl.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.DeleteTagAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { + return + } + + workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repository, tagCtl.Repo, tagCtl.PublicStorageConfig) + if err != nil { + w.Error(err) + return + } + + err = workRepo.CheckOut(ctx, versionmgr.InTag, params.RefName) + if err != nil { + w.Error(err) + return + } + + err = workRepo.DeleteTag(ctx) + if err != nil { + w.Error(err) + return + } + w.OK() +} + +func (tagCtl TagController) GetTag(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.GetTagParams) { + owner, err := tagCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + // Get repo + repository, err := tagCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetOwnerID(owner.ID).SetName(repositoryName)) + if err != nil { + w.Error(err) + return + } + + if !tagCtl.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ReadTagAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { + return + } + + // Get branch + ref, err := tagCtl.Repo.TagRepo().Get(ctx, models.NewGetTagParams().SetName(params.RefName).SetRepositoryID(repository.ID)) + if err != nil { + w.Error(err) + return + } + w.JSON(utils.Silent(tagToDto(ref))) +} + +func (tagCtl TagController) ListTags(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.ListTagsParams) { + owner, err := tagCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + repository, err := tagCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) + if err != nil { + w.Error(err) + return + } + + if !tagCtl.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ListTagsAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { + return + } + + listTagParams := models.NewListTagParams() + if params.Prefix != nil && len(*params.Prefix) > 0 { + listTagParams.SetName(*params.Prefix, models.PrefixMatch) + } + if params.After != nil { + listTagParams.SetAfter(time.UnixMilli(*params.After)) + } + + pageAmount := utils.IntValue(params.Amount) + if pageAmount > utils.DefaultMaxPerPage || pageAmount <= 0 { + listTagParams.SetAmount(utils.DefaultMaxPerPage) + } else { + listTagParams.SetAmount(pageAmount) + } + + tags, hasMore, err := tagCtl.Repo.TagRepo().List(ctx, listTagParams.SetRepositoryID(repository.ID)) + if err != nil { + w.Error(err) + return + } + results := utils.Silent(utils.ArrMap(tags, tagToDto)) + pagMag := utils.PaginationFor(hasMore, results, "UpdatedAt") + pagination := api.Pagination{ + HasMore: pagMag.HasMore, + MaxPerPage: pagMag.MaxPerPage, + NextOffset: pagMag.NextOffset, + Results: pagMag.Results, + } + w.JSON(api.TagList{ + Pagination: pagination, + Results: results, + }) +} + +func tagToDto(in *models.Tag) (api.Tag, error) { + return api.Tag{ + CreatedAt: in.CreatedAt.UnixMilli(), + Message: in.Message, + Name: in.Name, + RepositoryId: in.RepositoryID, + CreatorId: in.CreatorID, + Target: in.Target.Hex(), + UpdatedAt: in.UpdatedAt.UnixMilli(), + }, nil +} diff --git a/controller/validator/validate.go b/controller/validator/validate.go index 041c5246..7c6abee2 100644 --- a/controller/validator/validate.go +++ b/controller/validator/validate.go @@ -11,11 +11,12 @@ var ( ReValidRef = regexp.MustCompile(`^\w+/?\w+$`) ReValidRepo = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_\-]{1,61}[a-zA-Z0-9]$`) + ReValidTag = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_.\-]{1,61}[a-zA-Z0-9]$`) ReValidUser = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_-]{1,28}[a-zA-Z0-9]$`) ReValidPath = regexp.MustCompile(`^[^\x00/:*?"<>|]*/?([^/\s\x00:*?"<>|]+/)*[^/\s\x00:*?"<>|]+(?:\.[a-zA-Z0-9]+)?$`) // RepoNameBlackList forbid repo name, reserve for routes - RepoNameBlackList = []string{"repository", "repositories", "wip", "wips", "object", "objects", "commit", "commits", "ref", "refs", "repo", "repos", "user", "users"} + RepoNameBlackList = []string{"repository", "repositories", "wip", "wips", "object", "objects", "tags", "tag", "commit", "commits", "ref", "refs", "repo", "repos", "user", "users"} ) var ( @@ -24,6 +25,7 @@ var ( ErrBranchFormat = errors.New("branch format must be or /") ErrInvalidBranchName = errors.New("invalid branch name: must start with a number or letter and can only contain numbers, letters, hyphens or underscores") ErrInvalidRepoName = errors.New("repository name must start with a number or letter, can only contain numbers, letters, or hyphens, and must be between 3 and 63 characters in length") + ErrInvalidTagName = errors.New("tag name must start with a number or letter, can only contain numbers, letters, dot, or hyphens, and must be between 3 and 63 characters in length") ErrInvalidUsername = errors.New("invalid username: it must start and end with a letter or digit, can contain letters, digits, hyphens, and cannot start or end with a hyphen; the length must be between 3 and 30 characters") ErrInvalidObjectPath = errors.New("invalid object path: it must not contain null characters or NTFS forbidden characters") ) @@ -68,6 +70,13 @@ func ValidateRepoName(name string) error { return nil } +func ValidateTagName(name string) error { + if !ReValidTag.MatchString(name) { + return ErrInvalidTagName + } + return nil +} + func ValidateUsername(name string) error { if !ReValidUser.MatchString(name) { return ErrInvalidUsername diff --git a/controller/validator/validate_test.go b/controller/validator/validate_test.go index 4bfaf4f8..95dfe4ce 100644 --- a/controller/validator/validate_test.go +++ b/controller/validator/validate_test.go @@ -66,6 +66,34 @@ func TestValidateRepoName(t *testing.T) { } } +func TestValidateTagName(t *testing.T) { + //Validate Repo names + validTagNames := []string{"myrepo", "v00.00.01", "user123", "tag", "project-name", "repo123_name"} + for _, name := range validTagNames { + err := ValidateTagName(name) + if err != nil { + t.Errorf("Expected no error for repo name '%s', but got: %s", name, err) + } + } + + //Invalidate Repo names + invalidTagNames := []struct { + name string + }{ + {"invalidaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, + {"invalid/name"}, + {"分支/name"}, + {"私は支店です/name"}, + } + + for _, testCase := range invalidTagNames { + err := ValidateTagName(testCase.name) + if err == nil { + t.Errorf("expect error") + } + } +} + func TestValidateUsername(t *testing.T) { //Validate Username validUsernames := []string{"user123", "username", "user_name", "user-123"} diff --git a/integrationtest/branch_test.go b/integrationtest/branch_test.go index 4b7df54f..8634df4d 100644 --- a/integrationtest/branch_test.go +++ b/integrationtest/branch_test.go @@ -277,6 +277,5 @@ func BranchSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(getResp.StatusCode, convey.ShouldEqual, http.StatusNotFound) }) }) - } } diff --git a/integrationtest/commit_test.go b/integrationtest/commit_test.go index 9a4cb041..8c0e383f 100644 --- a/integrationtest/commit_test.go +++ b/integrationtest/commit_test.go @@ -228,6 +228,29 @@ func GetEntriesInRefSpec(ctx context.Context, urlStr string) func(c convey.C) { }) }) + c.Convey("get tag entries", func(c convey.C) { + tagName := "v0.0.1" + c.Convey("init", func() { + _ = createTag(ctx, client, userName, repoName, tagName, branchName) + }) + + c.Convey("success to get entries in root", func() { + resp, err := client.GetEntriesInRef(ctx, userName, repoName, &api.GetEntriesInRefParams{ + Path: utils.String("/"), + Ref: utils.String(tagName), + Type: api.RefTypeTag, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseGetEntriesInRefResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(*result.JSON200, convey.ShouldHaveLength, 2) + convey.So((*result.JSON200)[0].Name, convey.ShouldEqual, "g") + convey.So((*result.JSON200)[1].Name, convey.ShouldEqual, "m.dat") + }) + }) + c.Convey("prepare data for commit test", func(_ convey.C) { createWip(ctx, client, userName, repoName, "main") uploadObject(ctx, client, userName, repoName, "main", "a.dat", true) //delete\ diff --git a/integrationtest/helper_test.go b/integrationtest/helper_test.go index 7402ca0e..349e5202 100644 --- a/integrationtest/helper_test.go +++ b/integrationtest/helper_test.go @@ -158,6 +158,19 @@ func createBranch(ctx context.Context, client *api.Client, user string, repoName return result.JSON201 } +func createTag(ctx context.Context, client *api.Client, user string, repoName string, tagName, target string) *api.Branch { + resp, err := client.CreateTag(ctx, user, repoName, api.CreateTagJSONRequestBody{ + Name: tagName, + Target: target, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + + result, err := api.ParseCreateBranchResponse(resp) + convey.So(err, convey.ShouldBeNil) + return result.JSON201 +} + func createRepo(ctx context.Context, client *api.Client, repoName string, visible bool) *api.Repository { resp, err := client.CreateRepository(ctx, api.CreateRepositoryJSONRequestBody{ Name: repoName, @@ -220,6 +233,17 @@ func commitWip(ctx context.Context, client *api.Client, user string, repoName st return result.JSON201 } +func getBranch(ctx context.Context, client *api.Client, user string, repoName string, refName string) *api.Branch { + resp, err := client.GetBranch(ctx, user, repoName, &api.GetBranchParams{ + RefName: refName, + }) + convey.So(err, convey.ShouldBeNil) + + result, err := api.ParseGetBranchResponse(resp) + convey.So(err, convey.ShouldBeNil) + return result.JSON200 +} + func createMergeRequest(ctx context.Context, client *api.Client, user string, repoName string, sourceBranch string, targetBranch string) *api.MergeRequest { resp, err := client.CreateMergeRequest(ctx, user, repoName, api.CreateMergeRequestJSONRequestBody{ Description: utils.String("create merge request test"), diff --git a/integrationtest/root_test.go b/integrationtest/root_test.go index c528f792..2a002f3b 100644 --- a/integrationtest/root_test.go +++ b/integrationtest/root_test.go @@ -18,6 +18,7 @@ func TestSpec(t *testing.T) { convey.Convey("aksk test", t, AkSkSpec(ctx, urlStr)) convey.Convey("repo test", t, RepoSpec(ctx, urlStr)) convey.Convey("branch test", t, BranchSpec(ctx, urlStr)) + convey.Convey("tag test", t, TagSpec(ctx, urlStr)) convey.Convey("object test", t, ObjectSpec(ctx, urlStr)) convey.Convey("wip test", t, WipSpec(ctx, urlStr)) convey.Convey("wip object test", t, WipObjectSpec(ctx, urlStr)) diff --git a/integrationtest/tag_test.go b/integrationtest/tag_test.go new file mode 100644 index 00000000..af5de37b --- /dev/null +++ b/integrationtest/tag_test.go @@ -0,0 +1,331 @@ +package integrationtest + +import ( + "context" + "net/http" + "strconv" + "time" + + "github.com/GitDataAI/jiaozifs/api" + apiimpl "github.com/GitDataAI/jiaozifs/api/api_impl" + "github.com/GitDataAI/jiaozifs/utils" + "github.com/smartystreets/goconvey/convey" +) + +func TagSpec(ctx context.Context, urlStr string) func(c convey.C) { + client, _ := api.NewClient(urlStr + apiimpl.APIV1Prefix) + return func(c convey.C) { + userName := "tagUser" + repoName := "tagTest" + tagName := "v00.00.01" + + c.Convey("init", func(_ convey.C) { + _ = createUser(ctx, client, userName) + loginAndSwitch(ctx, client, userName, false) + _ = createRepo(ctx, client, repoName, false) + _ = createWip(ctx, client, userName, repoName, "main") + _ = uploadObject(ctx, client, userName, repoName, "main", "a.txt", true) + _ = uploadObject(ctx, client, userName, repoName, "main", "b.txt", true) + _ = uploadObject(ctx, client, userName, repoName, "main", "c.txt", true) + _ = commitWip(ctx, client, userName, repoName, "main", "commit wip") + }) + + c.Convey("create tag", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.CreateTag(ctx, userName, repoName, api.CreateTagJSONRequestBody{ + Name: tagName, + Message: utils.String("testv"), + Target: "main", + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("fail to create tag in non exit user", func() { + resp, err := client.CreateTag(ctx, "fakeUser", repoName, api.CreateTagJSONRequestBody{ + Name: tagName, + Message: utils.String("testv"), + Target: "main", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to create tag in non exit repo", func() { + resp, err := client.CreateTag(ctx, userName, "fakerepo", api.CreateTagJSONRequestBody{ + Name: tagName, + Message: utils.String("testv"), + Target: "main", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to create tag in non exit branch or wrong commit hash", func() { + resp, err := client.CreateTag(ctx, userName, repoName, api.CreateTagJSONRequestBody{ + Name: tagName, + Message: utils.String("testv"), + Target: "main_not_exist", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusInternalServerError) + }) + + c.Convey("fail to create tag with not exit commit", func() { + resp, err := client.CreateTag(ctx, userName, repoName, api.CreateTagJSONRequestBody{ + Name: tagName, + Message: utils.String("testv"), + Target: "963362e15e39cbb92203641b8eeb5e7b", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("too long name", func() { + resp, err := client.CreateTag(ctx, userName, repoName, api.CreateTagJSONRequestBody{ + Name: "v00.00.01111111111111111111111111111111111111111111111111111111111111111111111111111", + Message: utils.String("testv"), + Target: "main", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusBadRequest) + }) + + c.Convey("target not found", func() { + resp, err := client.CreateTag(ctx, userName, repoName, api.CreateTagJSONRequestBody{ + Name: tagName, + Message: utils.String("testv"), + Target: "1111", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("forbidden create tag in others", func() { + resp, err := client.CreateTag(ctx, "jimmy", "happygo", api.CreateTagJSONRequestBody{ + Name: tagName, + Message: utils.String("testv"), + Target: "main", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("success create tag from branch", func() { + resp, err := client.CreateTag(ctx, userName, repoName, api.CreateTagJSONRequestBody{ + Name: tagName, + Message: utils.String("testv"), + Target: "main", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + }) + + c.Convey("success create tag from commit", func() { + branch := getBranch(ctx, client, userName, repoName, "main") + resp, err := client.CreateTag(ctx, userName, repoName, api.CreateTagJSONRequestBody{ + Name: "v00.00.02", + Message: utils.String("testv"), + Target: branch.CommitHash, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusCreated) + }) + }) + + c.Convey("get tag", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.GetTag(ctx, userName, repoName, &api.GetTagParams{ + RefName: tagName, + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("success get tag", func() { + resp, err := client.GetTag(ctx, userName, repoName, &api.GetTagParams{ + RefName: tagName, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + respResult, err := api.ParseGetTagResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(respResult.JSON200.Name, convey.ShouldEqual, tagName) + }) + + c.Convey("fail to get non exit ref", func() { + resp, err := client.GetTag(ctx, userName, repoName, &api.GetTagParams{ + RefName: "mock_ref", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to get ref from non exit user", func() { + resp, err := client.GetTag(ctx, "mock_owner", repoName, &api.GetTagParams{ + RefName: "main", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to get non exit branch", func() { + resp, err := client.GetTag(ctx, userName, "mock_repo", &api.GetTagParams{ + RefName: "main", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to others branch", func() { + resp, err := client.GetTag(ctx, "jimmy", "happygo", &api.GetTagParams{ + RefName: "main", + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + }) + + c.Convey("delete tag", func(c convey.C) { + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.DeleteTag(ctx, userName, repoName, &api.DeleteTagParams{RefName: tagName}) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("delete tag in not exit repo", func() { + resp, err := client.DeleteTag(ctx, userName, "falerepo", &api.DeleteTagParams{RefName: tagName}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("delete tag in non exit user", func() { + resp, err := client.DeleteTag(ctx, "fakeuser", repoName, &api.DeleteTagParams{RefName: tagName}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("delete tag in other's repo", func() { + resp, err := client.DeleteTag(ctx, "jimmy", "happygo", &api.DeleteTagParams{RefName: tagName}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("delete tag successful", func() { + resp, err := client.DeleteTag(ctx, userName, repoName, &api.DeleteTagParams{RefName: tagName}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + getResp, err := client.GetTag(ctx, userName, repoName, &api.GetTagParams{RefName: tagName}) + convey.So(err, convey.ShouldBeNil) + convey.So(getResp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + }) + + c.Convey("list tag", func(c convey.C) { + repo2Name := "tagTest2" + prefix := "vt" + c.Convey("init", func() { + _ = createRepo(ctx, client, repo2Name, false) + _ = createWip(ctx, client, userName, repo2Name, "main") + _ = uploadObject(ctx, client, userName, repo2Name, "main", "b.txt", true) + _ = commitWip(ctx, client, userName, repo2Name, "main", "commit wip") + createTag(ctx, client, userName, repo2Name, prefix+"00.00.01", "main") + createTag(ctx, client, userName, repo2Name, prefix+"00.00.02", "main") + createTag(ctx, client, userName, repo2Name, prefix+"00.00.03", "main") + createTag(ctx, client, userName, repo2Name, prefix+"00.00.04", "main") + createTag(ctx, client, userName, repo2Name, prefix+"00.00.05", "main") + createTag(ctx, client, userName, repo2Name, prefix+"00.00.06", "main") + createTag(ctx, client, userName, repo2Name, prefix+"00.00.07", "main") + createTag(ctx, client, userName, repo2Name, prefix+"00.00.08", "main") + createTag(ctx, client, userName, repo2Name, prefix+"00.00.09", "main") + }) + + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.ListTags(ctx, userName, repo2Name, &api.ListTagsParams{}) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("fail to list branchs from non exit user", func() { + resp, err := client.ListTags(ctx, "mock_owner", repo2Name, &api.ListTagsParams{}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to list branchs in non exit repo", func() { + resp, err := client.ListTags(ctx, userName, "mockrepo", &api.ListTagsParams{}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("fail to list branches in others repo", func() { + resp, err := client.ListTags(ctx, "jimmy", "happygo", &api.ListTagsParams{}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("success list branch", func() { + resp, err := client.ListTags(ctx, userName, repo2Name, &api.ListTagsParams{}) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + respResult, err := api.ParseListBranchesResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(respResult.JSON200.Results, convey.ShouldHaveLength, 9) + }) + + c.Convey("success list branch by prefix", func() { + resp, err := client.ListTags(ctx, userName, repo2Name, &api.ListTagsParams{ + Prefix: &prefix, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + respResult, err := api.ParseListBranchesResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.So(respResult.JSON200.Results, convey.ShouldHaveLength, 9) + }) + + c.Convey("success list branch paganation", func() { + var after *int64 + for i := 0; i < 5; i++ { + resp, err := client.ListTags(ctx, userName, repo2Name, &api.ListTagsParams{ + After: after, + Amount: utils.Int(2), + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseListTagsResponse(resp) + convey.So(err, convey.ShouldBeNil) + convey.ShouldHaveLength(*result.JSON200, 2) + if i >= 5 { + convey.ShouldBeFalse((*result.JSON200).Pagination.HasMore) + } else { + convey.ShouldBeTrue((*result.JSON200).Pagination.HasMore) + val, err := strconv.ParseInt((*result.JSON200).Pagination.NextOffset, 10, 64) + convey.So(err, convey.ShouldBeNil) + next := time.UnixMilli(val) + after = utils.Int64(next.UnixMilli()) + } + } + + }) + }) + + } +} diff --git a/models/rbacmodel/actions.gen.go b/models/rbacmodel/actions.gen.go index fe331aca..a9299f67 100644 --- a/models/rbacmodel/actions.gen.go +++ b/models/rbacmodel/actions.gen.go @@ -19,8 +19,13 @@ var Actions = []string{ "repo:CreateBranch", "repo:DeleteBranch", "repo:ReadBranch", - "repo:ReadBranch", + "repo:WriteBranch", "repo:ListBranches", + "repo:CreateTag", + "repo:DeleteTag", + "repo:ReadTag", + "repo:WriteTag", + "repo:ListTags", "repo:GetWip", "repo:ListWip", "repo:WriteWip", diff --git a/models/rbacmodel/actions.go b/models/rbacmodel/actions.go index 13dc926e..03d4c548 100644 --- a/models/rbacmodel/actions.go +++ b/models/rbacmodel/actions.go @@ -34,9 +34,15 @@ const ( CreateBranchAction = "repo:CreateBranch" DeleteBranchAction = "repo:DeleteBranch" ReadBranchAction = "repo:ReadBranch" - WriteBranchAction = "repo:ReadBranch" + WriteBranchAction = "repo:WriteBranch" ListBranchesAction = "repo:ListBranches" + CreateTagAction = "repo:CreateTag" + DeleteTagAction = "repo:DeleteTag" + ReadTagAction = "repo:ReadTag" + WriteTagAction = "repo:WriteTag" + ListTagsAction = "repo:ListTags" + ReadWipAction = "repo:GetWip" ListWipAction = "repo:ListWip" WriteWipAction = "repo:WriteWip" diff --git a/models/repo.go b/models/repo.go index f1a053a1..ec1c2622 100644 --- a/models/repo.go +++ b/models/repo.go @@ -32,7 +32,7 @@ type IRepo interface { MergeRequestRepo() IMergeRequestRepo FileTreeRepo(repoID uuid.UUID) IFileTreeRepo CommitRepo(repoID uuid.UUID) ICommitRepo - TagRepo(repoID uuid.UUID) ITagRepo + TagRepo() ITagRepo BranchRepo() IBranchRepo RepositoryRepo() IRepositoryRepo WipRepo() IWipRepo @@ -84,8 +84,8 @@ func (repo *PgRepo) CommitRepo(repoID uuid.UUID) ICommitRepo { return NewCommitRepo(repo.db, repoID) } -func (repo *PgRepo) TagRepo(repoID uuid.UUID) ITagRepo { - return NewTagRepo(repo.db, repoID) +func (repo *PgRepo) TagRepo() ITagRepo { + return NewTagRepo(repo.db) } func (repo *PgRepo) BranchRepo() IBranchRepo { diff --git a/models/tag.go b/models/tag.go index 989bbf2a..3f21f590 100644 --- a/models/tag.go +++ b/models/tag.go @@ -11,49 +11,113 @@ import ( type Tag struct { bun.BaseModel `bun:"table:tags"` - Hash hash.Hash `bun:"hash,pk,type:bytea" json:"hash"` - RepositoryID uuid.UUID `bun:"repository_id,pk,type:uuid,notnull" json:"repository_id"` - Type ObjectType `bun:"type" json:"type"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()" json:"id"` + RepositoryID uuid.UUID `bun:"repository_id,pk,type:uuid,unique:repo_id_name,notnull" json:"repository_id"` //////********commit********//////// // Name of the tag. - Name string `bun:"name" json:"name"` - // Tagger is the one who created the tag. - Tagger Signature `bun:"tagger,type:jsonb" json:"tagger"` - // TargetType is the object type of the target. - TargetType ObjectType `bun:"target_type" json:"target_type"` + Name string `bun:"name,unique:repo_id_name," json:"name"` + // Creator is the one who created the tag. + CreatorID uuid.UUID `bun:"tagger,type:uuid" json:"tagger"` // Target is the hash of the target object. Target hash.Hash `bun:"target,type:bytea" json:"target"` // Message is the tag message, contains arbitrary text. - Message string `bun:"message" json:"message"` + Message *string `bun:"message" json:"message"` CreatedAt time.Time `bun:"created_at,type:timestamp,notnull" json:"created_at"` UpdatedAt time.Time `bun:"updated_at,type:timestamp,notnull" json:"updated_at"` } -type ITagRepo interface { - RepositoryID() uuid.UUID - Insert(ctx context.Context, tag *Tag) (*Tag, error) - Tag(ctx context.Context, hash hash.Hash) (*Tag, error) - Delete(ctx context.Context, params *DeleteParams) (int64, error) +type GetTagParams struct { + id uuid.UUID + repositoryID uuid.UUID + name *string } -type TagRepo struct { - db bun.IDB +func NewGetTagParams() *GetTagParams { + return &GetTagParams{} +} + +func (gup *GetTagParams) SetID(id uuid.UUID) *GetTagParams { + gup.id = id + return gup +} + +func (gup *GetTagParams) SetRepositoryID(repositoryID uuid.UUID) *GetTagParams { + gup.repositoryID = repositoryID + return gup +} + +func (gup *GetTagParams) SetName(name string) *GetTagParams { + gup.name = &name + return gup +} + +type DeleteTagParams struct { + id uuid.UUID repositoryID uuid.UUID } -func NewTagRepo(db bun.IDB, repID uuid.UUID) ITagRepo { - return &TagRepo{db: db, repositoryID: repID} +func NewDeleteTagParams() *DeleteTagParams { + return &DeleteTagParams{} +} + +func (gup *DeleteTagParams) SetRepositoryID(repositoryID uuid.UUID) *DeleteTagParams { + gup.repositoryID = repositoryID + return gup +} +func (gup *DeleteTagParams) SetID(id uuid.UUID) *DeleteTagParams { + gup.id = id + return gup +} + +type ListTagParams struct { + RepositoryID uuid.UUID + Name *string + NameMatch MatchMode + After *time.Time + Amount int +} + +func NewListTagParams() *ListTagParams { + return &ListTagParams{} +} + +func (gup *ListTagParams) SetRepositoryID(repositoryID uuid.UUID) *ListTagParams { + gup.RepositoryID = repositoryID + return gup +} + +func (gup *ListTagParams) SetName(name string, match MatchMode) *ListTagParams { + gup.Name = &name + gup.NameMatch = match + return gup +} + +func (gup *ListTagParams) SetAfter(after time.Time) *ListTagParams { + gup.After = &after + return gup +} + +func (gup *ListTagParams) SetAmount(amount int) *ListTagParams { + gup.Amount = amount + return gup } -func (t *TagRepo) RepositoryID() uuid.UUID { - return t.repositoryID +type ITagRepo interface { + Insert(ctx context.Context, tag *Tag) (*Tag, error) + Get(ctx context.Context, params *GetTagParams) (*Tag, error) + Delete(ctx context.Context, params *DeleteTagParams) (int64, error) + List(ctx context.Context, params *ListTagParams) ([]*Tag, bool, error) } +type TagRepo struct { + db bun.IDB +} + +func NewTagRepo(db bun.IDB) ITagRepo { + return &TagRepo{db: db} +} func (t *TagRepo) Insert(ctx context.Context, tag *Tag) (*Tag, error) { - if tag.RepositoryID != t.repositoryID { - return nil, ErrRepoIDMisMatch - } _, err := t.db.NewInsert(). Model(tag). Exec(ctx) @@ -63,23 +127,68 @@ func (t *TagRepo) Insert(ctx context.Context, tag *Tag) (*Tag, error) { return tag, nil } -func (t *TagRepo) Tag(ctx context.Context, hash hash.Hash) (*Tag, error) { +func (t *TagRepo) Get(ctx context.Context, params *GetTagParams) (*Tag, error) { tag := &Tag{} - err := t.db.NewSelect(). - Model(tag). - Where("repository_id = ?", t.repositoryID). - Where("hash = ?", hash). - Scan(ctx) + query := t.db.NewSelect().Model(tag) + + if uuid.Nil != params.id { + query = query.Where("id = ?", params.id) + } + + if uuid.Nil != params.repositoryID { + query = query.Where("repository_id = ?", params.repositoryID) + } + + if params.name != nil { + query = query.Where("name = ?", *params.name) + } + + err := query.Limit(1).Scan(ctx) if err != nil { return nil, err } return tag, nil } -func (t *TagRepo) Delete(ctx context.Context, params *DeleteParams) (int64, error) { - query := t.db.NewDelete().Model((*Tag)(nil)).Where("repository_id = ?", t.repositoryID) - if params.hash != nil { - query = query.Where("hash = ?", params.hash) +func (t *TagRepo) List(ctx context.Context, params *ListTagParams) ([]*Tag, bool, error) { + var tags []*Tag + query := t.db.NewSelect().Model(&tags) + + if uuid.Nil != params.RepositoryID { + query = query.Where("repository_id = ?", params.RepositoryID) + } + + if params.Name != nil { + switch params.NameMatch { + case ExactMatch: + query = query.Where("name = ?", *params.Name) + case PrefixMatch: + query = query.Where("name LIKE ?", *params.Name+"%") + case SuffixMatch: + query = query.Where("name LIKE ?", "%"+*params.Name) + case LikeMatch: + query = query.Where("name LIKE ?", "%"+*params.Name+"%") + } + } + + query = query.Order("updated_at DESC") + if params.After != nil { + query = query.Where("updated_at > ?", *params.After) + } + + err := query.Limit(params.Amount).Scan(ctx) + return tags, len(tags) == params.Amount, err +} + +func (t *TagRepo) Delete(ctx context.Context, params *DeleteTagParams) (int64, error) { + query := t.db.NewDelete().Model((*Tag)(nil)) + + if uuid.Nil != params.id { + query = query.Where("id = ?", params.id) + } + + if uuid.Nil != params.repositoryID { + query = query.Where("repository_id = ?", params.repositoryID) } sqlResult, err := query.Exec(ctx) diff --git a/models/tag_test.go b/models/tag_test.go index c7f94744..3d3e43f4 100644 --- a/models/tag_test.go +++ b/models/tag_test.go @@ -3,6 +3,7 @@ package models_test import ( "context" "testing" + "time" "github.com/GitDataAI/jiaozifs/models" "github.com/GitDataAI/jiaozifs/testhelper" @@ -12,68 +13,88 @@ import ( "github.com/stretchr/testify/require" ) -func TestTagRepo(t *testing.T) { +func TestTagRepoInsert(t *testing.T) { ctx := context.Background() closeDB, _, db := testhelper.SetupDatabase(ctx, t) defer closeDB() - repoID := uuid.New() - tagRepo := models.NewTagRepo(db, repoID) - require.Equal(t, tagRepo.RepositoryID(), repoID) + repo := models.NewTagRepo(db) tagModel := &models.Tag{} require.NoError(t, gofakeit.Struct(tagModel)) - tagModel.RepositoryID = repoID - newTagModel, err := tagRepo.Insert(ctx, tagModel) + tagModel.Name = "atagName" + tagModel.UpdatedAt = time.Now() + newTag, err := repo.Insert(ctx, tagModel) require.NoError(t, err) - tagModel, err = tagRepo.Tag(ctx, tagModel.Hash) + require.NotEqual(t, uuid.Nil, newTag.ID) + + getTagParams := models.NewGetTagParams(). + SetID(newTag.ID). + SetRepositoryID(newTag.RepositoryID). + SetName(newTag.Name) + branch, err := repo.Get(ctx, getTagParams) require.NoError(t, err) - require.True(t, cmp.Equal(tagModel, newTagModel, testhelper.DBTimeCmpOpt)) + require.True(t, cmp.Equal(tagModel, branch, testhelper.DBTimeCmpOpt)) - t.Run("mis match repo id", func(t *testing.T) { - mistMatchModel := &models.Tag{} - require.NoError(t, gofakeit.Struct(mistMatchModel)) - _, err := tagRepo.Insert(ctx, mistMatchModel) - require.ErrorIs(t, err, models.ErrRepoIDMisMatch) - }) -} + list, _, err := repo.List(ctx, models.NewListTagParams().SetRepositoryID(branch.RepositoryID)) + require.NoError(t, err) + require.Len(t, list, 1) -func TestDeleteTag(t *testing.T) { - ctx := context.Background() - closeDB, _, db := testhelper.SetupDatabase(ctx, t) - defer closeDB() - t.Run("delete tag", func(t *testing.T) { - repoID := uuid.New() - tagRepo := models.NewTagRepo(db, repoID) - require.Equal(t, tagRepo.RepositoryID(), repoID) - - toDeleteModel := &models.Tag{} - require.NoError(t, gofakeit.Struct(toDeleteModel)) - toDeleteModel.RepositoryID = repoID - toDeleteModel, err := tagRepo.Insert(ctx, toDeleteModel) - require.NoError(t, err) - - affectRows, err := tagRepo.Delete(ctx, models.NewDeleteParams().SetHash(toDeleteModel.Hash)) - require.NoError(t, err) - require.Equal(t, int64(1), affectRows) - }) - - t.Run("delete tags batch", func(t *testing.T) { - repoID := uuid.New() - tagRepo := models.NewTagRepo(db, repoID) - require.Equal(t, tagRepo.RepositoryID(), repoID) - - for i := 0; i < 5; i++ { - toDeleteModel := &models.Tag{} - require.NoError(t, gofakeit.Struct(toDeleteModel)) - toDeleteModel.RepositoryID = repoID - _, err := tagRepo.Insert(ctx, toDeleteModel) - require.NoError(t, err) - } - - affectRows, err := tagRepo.Delete(ctx, models.NewDeleteParams()) - require.NoError(t, err) - require.Equal(t, int64(5), affectRows) - }) + // SecondModel + secModel := &models.Tag{} + require.NoError(t, gofakeit.Struct(secModel)) + secModel.RepositoryID = branch.RepositoryID + secModel.Name = "feat_bba_ccc" + secModel.UpdatedAt = time.Now() + secRef, err := repo.Insert(ctx, secModel) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, secRef.ID) + + getSecRefParams := models.NewGetTagParams(). + SetID(secRef.ID). + SetRepositoryID(secRef.RepositoryID). + SetName(secRef.Name) + sRef, err := repo.Get(ctx, getSecRefParams) + require.NoError(t, err) + + require.True(t, cmp.Equal(secModel, sRef, testhelper.DBTimeCmpOpt)) + + // ExactMatch + list1, hasMore, err := repo.List(ctx, models.NewListTagParams().SetRepositoryID(branch.RepositoryID).SetName(secModel.Name, models.ExactMatch).SetAmount(1)) + require.NoError(t, err) + require.Len(t, list1, 1) + require.True(t, hasMore) + + // PrefixMatch + list2, hasMore, err := repo.List(ctx, models.NewListTagParams().SetRepositoryID(branch.RepositoryID).SetName(secModel.Name[:3], models.PrefixMatch).SetAmount(1)) + require.NoError(t, err) + require.Len(t, list2, 1) + require.True(t, hasMore) + + // SuffixMatch + list3, hasMore, err := repo.List(ctx, models.NewListTagParams().SetRepositoryID(branch.RepositoryID).SetName(secModel.Name[3:], models.SuffixMatch).SetAmount(1)) + require.NoError(t, err) + require.Len(t, list3, 1) + require.True(t, hasMore) + + // LikeMatch + list4, hasMore, err := repo.List(ctx, models.NewListTagParams().SetRepositoryID(branch.RepositoryID).SetName(secModel.Name[2:4], models.LikeMatch).SetAmount(1)) + require.NoError(t, err) + require.Len(t, list4, 1) + require.True(t, hasMore) + + // After + list5, hasMore, err := repo.List(ctx, models.NewListTagParams().SetRepositoryID(branch.RepositoryID).SetAfter(tagModel.UpdatedAt)) + require.NoError(t, err) + require.Len(t, list5, 1) + require.False(t, hasMore) + + affectedRows, err := repo.Delete(ctx, models.NewDeleteTagParams().SetRepositoryID(list[0].RepositoryID).SetID(secModel.ID)) + require.NoError(t, err) + require.Equal(t, int64(1), affectedRows) + + list6, _, err := repo.List(ctx, models.NewListTagParams().SetRepositoryID(branch.RepositoryID)) + require.NoError(t, err) + require.Len(t, list6, 1) } diff --git a/versionmgr/work_repo.go b/versionmgr/work_repo.go index b06b2bd2..ce06c13c 100644 --- a/versionmgr/work_repo.go +++ b/versionmgr/work_repo.go @@ -31,6 +31,7 @@ const ( InWip WorkRepoState = "wip" InBranch WorkRepoState = "branch" InCommit WorkRepoState = "commit" + InTag WorkRepoState = "tag" ) type WorkRepository struct { @@ -43,6 +44,7 @@ type WorkRepository struct { headTree *hash.Hash wip *models.WorkingInProcess branch *models.Branch + tag *models.Tag commit *models.Commit } @@ -161,7 +163,7 @@ func (repository *WorkRepository) rootTree(ctx context.Context, repo models.IRep } treeHash = commit.TreeHash } - repository.setCurState(InBranch, nil, ref, commit) + repository.setCurState(InBranch, nil, ref, nil, commit) repository.headTree = &treeHash } return NewWorkTree(ctx, repo.FileTreeRepo(repository.repoModel.ID), models.NewRootTreeEntry(*repository.headTree)) @@ -179,7 +181,7 @@ func (repository *WorkRepository) CheckOut(ctx context.Context, refType WorkRepo return fmt.Errorf("unable to get wip of repository %s branch %s: %w", repository.repoModel.Name, refName, err) } treeHash = wip.CurrentTree - repository.setCurState(InWip, wip, ref, nil) + repository.setCurState(InWip, wip, ref, nil, nil) } else if refType == InBranch { branch, err := repository.repo.BranchRepo().Get(ctx, models.NewGetBranchParams().SetRepositoryID(repository.repoModel.ID).SetName(refName)) if err != nil { @@ -193,7 +195,7 @@ func (repository *WorkRepository) CheckOut(ctx context.Context, refType WorkRepo } treeHash = commit.TreeHash } - repository.setCurState(InBranch, nil, branch, commit) + repository.setCurState(InBranch, nil, branch, nil, commit) } else if refType == InCommit { commitHash, err := hash.FromHex(refName) if err != nil { @@ -206,8 +208,19 @@ func (repository *WorkRepository) CheckOut(ctx context.Context, refType WorkRepo return err } treeHash = commit.TreeHash - repository.setCurState(InCommit, nil, nil, commit) + repository.setCurState(InCommit, nil, nil, nil, commit) } + } else if refType == InTag { + tag, err := repository.repo.TagRepo().Get(ctx, models.NewGetTagParams().SetRepositoryID(repository.repoModel.ID).SetName(refName)) + if err != nil { + return err + } + commit, err := repository.repo.CommitRepo(repository.repoModel.ID).Commit(ctx, tag.Target) + if err != nil { + return err + } + treeHash = commit.TreeHash + repository.setCurState(InTag, nil, nil, tag, commit) } else { return fmt.Errorf("not support type") } @@ -422,6 +435,7 @@ func (repository *WorkRepository) ChangeAndCommit(ctx context.Context, msg strin } repository.branch.CommitHash = commit.Hash repository.wip.BaseCommit = commit.Hash + //dont set commit here, wip possibly wip changes return commit, err } @@ -509,21 +523,10 @@ func (repository *WorkRepository) CreateBranch(ctx context.Context, branchName s // DeleteBranch delete branch also delete wip belong this branch func (repository *WorkRepository) DeleteBranch(ctx context.Context) error { return repository.repo.Transaction(ctx, func(repo models.IRepo) error { - wips, err := repo.WipRepo().List(ctx, models.NewListWipParams().SetRepositoryID(repository.repoModel.ID).SetRefID(repository.branch.ID)) - if err != nil { - return err - } - for _, wip := range wips { - _, err = repo.WipRepo().Delete(ctx, models.NewDeleteWipParams().SetID(wip.ID)) - if err != nil { - return err - } - } - deleteBranchParams := models.NewDeleteBranchParams(). SetRepositoryID(repository.repoModel.ID). SetName(repository.branch.Name) - _, err = repo.BranchRepo().Delete(ctx, deleteBranchParams) + _, err := repo.BranchRepo().Delete(ctx, deleteBranchParams) if err != nil { return err } @@ -538,6 +541,52 @@ func (repository *WorkRepository) DeleteBranch(ctx context.Context) error { }) } +// CreateTag create tag base on current head +func (repository *WorkRepository) CreateTag(ctx context.Context, tagName string, msg *string) (*models.Tag, error) { + //check exit + _, err := repository.repo.TagRepo().Get(ctx, models.NewGetTagParams().SetName(tagName).SetRepositoryID(repository.repoModel.ID)) + if err == nil { + return nil, fmt.Errorf("%s already exit", tagName) + } + + if err != nil && !errors.Is(err, models.ErrNotFound) { + return nil, err + } + + if repository.commit == nil { + return nil, fmt.Errorf("no commit to create tag") + } + + commitHash := repository.commit.Hash + if commitHash.IsEmpty() { + return nil, fmt.Errorf("empty commit to create tag") + } + + // Create branch + newTag := &models.Tag{ + CreatorID: repository.operator.ID, + Target: commitHash, + Message: msg, + RepositoryID: repository.repoModel.ID, + Name: tagName, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + return repository.repo.TagRepo().Insert(ctx, newTag) +} + +// DeleteTag delete tag +func (repository *WorkRepository) DeleteTag(ctx context.Context) error { + return repository.repo.Transaction(ctx, func(repo models.IRepo) error { + delTagParams := models.NewDeleteTagParams(). + SetRepositoryID(repository.repoModel.ID). + SetID(repository.tag.ID) + _, err := repo.TagRepo().Delete(ctx, delTagParams) + return err + }) +} + +// GetOrCreateWip get wip if exited, otherwise create one func (repository *WorkRepository) GetOrCreateWip(ctx context.Context) (*models.WorkingInProcess, bool, error) { if repository.state != InBranch { return nil, false, fmt.Errorf("only create wip from branch") @@ -578,7 +627,7 @@ func (repository *WorkRepository) GetOrCreateWip(ctx context.Context) (*models.W return nil, false, err } repository.headTree = &wip.CurrentTree - repository.setCurState(InWip, wip, repository.branch, nil) + repository.setCurState(InWip, wip, repository.branch, nil, nil) return wip, true, nil } @@ -730,24 +779,32 @@ func (repository *WorkRepository) Merge(ctx context.Context, toMergeCommitHash h return newCommit, nil } -func (repository *WorkRepository) setCurState(state WorkRepoState, wip *models.WorkingInProcess, branch *models.Branch, commit *models.Commit) { +func (repository *WorkRepository) setCurState(state WorkRepoState, wip *models.WorkingInProcess, branch *models.Branch, tag *models.Tag, commit *models.Commit) { repository.state = state repository.wip = wip repository.branch = branch + repository.tag = tag repository.commit = commit } +// CurWip return current wip if in wip, else return nil func (repository *WorkRepository) CurWip() *models.WorkingInProcess { return repository.wip } +// CurBranch return current branch if in branch, else return nil func (repository *WorkRepository) CurBranch() *models.Branch { return repository.branch } +// CurTag return current tag if in tag, else return nil +func (repository *WorkRepository) CurTag() *models.Branch { + return repository.branch +} + func (repository *WorkRepository) Reset() { repository.headTree = nil - repository.setCurState("", nil, nil, nil) + repository.setCurState("", nil, nil, nil, nil) } func findBestAncestor(ctx context.Context, diff --git a/versionmgr/work_repo_test.go b/versionmgr/work_repo_test.go index c25c8376..224823f1 100644 --- a/versionmgr/work_repo_test.go +++ b/versionmgr/work_repo_test.go @@ -367,9 +367,7 @@ func TestWorkRepositoryMergeState(t *testing.T) { project, err := makeRepository(ctx, repo, user, t.Name()) require.NoError(t, err) - testData1 := ` -1|a.txt |a -` + testData1 := `1|a.txt |a` workRepo := NewWorkRepositoryFromAdapter(ctx, user, project, repo, adapter) //base branch @@ -534,6 +532,47 @@ func TestWorkRepositoryMergeState(t *testing.T) { }) } +func TestWorkRepositoryCreateTag(t *testing.T) { + ctx := context.Background() + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() + repo := models.NewRepo(db) + + user, err := makeUser(ctx, repo.UserRepo(), "admin") + require.NoError(t, err) + + project, err := makeRepository(ctx, repo, user, t.Name()) + require.NoError(t, err) + + adapter := mem.New(ctx) + workRepo := NewWorkRepositoryFromAdapter(ctx, user, project, repo, adapter) + + err = workRepo.CheckOut(ctx, InBranch, "main") + require.NoError(t, err) + + _, err = workRepo.CreateTag(ctx, "v0.0.1", nil) + require.Error(t, err) + + testData1 := ` +1|a.txt |a +` + _, err = addChangesToWip(ctx, workRepo, "main", "", testData1) + require.NoError(t, err) + + _, err = workRepo.CreateTag(ctx, "v0.0.1", nil) + require.Error(t, err) + + err = workRepo.CheckOut(ctx, InBranch, "main") + require.NoError(t, err) + + _, err = workRepo.CreateTag(ctx, "v0.0.1", nil) + require.NoError(t, err) + + //duplicate tag + _, err = workRepo.CreateTag(ctx, "v0.0.1", nil) + require.Error(t, err) +} + func makeUser(ctx context.Context, userRepo models.IUserRepo, name string) (*models.User, error) { user := &models.User{ Name: name, From 8237cb143617ca35fd4174e289d67e89750d2a26 Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Sat, 23 Mar 2024 20:46:06 +0800 Subject: [PATCH 203/210] fix/mainfest_patter_clean (#160) --- versionmgr/worktree.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versionmgr/worktree.go b/versionmgr/worktree.go index 9d9ede08..60e96004 100644 --- a/versionmgr/worktree.go +++ b/versionmgr/worktree.go @@ -450,7 +450,7 @@ type TreeManifest struct { func (workTree *WorkTree) GetTreeManifest(ctx context.Context, pattern string) (TreeManifest, error) { //todo match all files, it maybe slow maybe need a new algo like filepath.Glob - pattern = strings.ReplaceAll(pattern, "\\", "/") + pattern = CleanPath(pattern) wk := FileWalk{curNode: workTree.root, object: workTree.object} g, err := glob.Compile(pattern) if err != nil { From 2f4cbd78ed9eed6ecf59493a4a0532028eee5514 Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Sun, 24 Mar 2024 15:05:20 +0800 Subject: [PATCH 204/210] feat: add api for get archive repo data (#161) --- api/custom_response.go | 4 +- api/jiaozifs.gen.go | 479 +++++++++++++++++++++++++------- api/swagger.yml | 74 +++++ auth/aksk/verifier_test.go | 17 +- controller/repository_ctl.go | 67 +++++ go.mod | 16 +- go.sum | 8 + integrationtest/repo_test.go | 126 ++++++++- utils/io.go | 17 ++ versionmgr/archive_repo.go | 169 +++++++++++ versionmgr/archive_repo_test.go | 176 ++++++++++++ versionmgr/files_walk.go | 17 +- versionmgr/files_walk_test.go | 35 ++- versionmgr/work_repo.go | 49 ++++ versionmgr/work_repo_test.go | 83 ++++-- versionmgr/worktree.go | 6 +- 16 files changed, 1185 insertions(+), 158 deletions(-) create mode 100644 utils/io.go create mode 100644 versionmgr/archive_repo.go create mode 100644 versionmgr/archive_repo_test.go diff --git a/api/custom_response.go b/api/custom_response.go index 45956f01..ad631ebe 100644 --- a/api/custom_response.go +++ b/api/custom_response.go @@ -51,9 +51,9 @@ func (response *JiaozifsResponse) Unauthorized() { response.WriteHeader(http.StatusUnauthorized) } -func (response *JiaozifsResponse) BadRequest(msg string) { +func (response *JiaozifsResponse) BadRequest(msg string, args ...any) { response.WriteHeader(http.StatusBadRequest) - _, _ = response.Write([]byte(msg)) + _, _ = response.Write([]byte(fmt.Sprintf(msg, args...))) } // Error response with 500 and error message diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 2f1af506..017494b0 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -33,6 +33,12 @@ const ( Jwt_tokenScopes = "jwt_token.Scopes" ) +// Defines values for ArchiveType. +const ( + Car ArchiveType = "car" + Zip ArchiveType = "zip" +) + // Defines values for ChangeAction. const ( N1 ChangeAction = 1 @@ -76,6 +82,9 @@ type AkskList struct { Results []SafeAksk `json:"results"` } +// ArchiveType defines model for ArchiveType. +type ArchiveType string + // AuthenticationToken defines model for AuthenticationToken. type AuthenticationToken struct { // Token a JWT token that could be used to authenticate requests @@ -542,6 +551,18 @@ type DeleteRepositoryParams struct { IsCleanData *bool `form:"is_clean_data,omitempty" json:"is_clean_data,omitempty"` } +// GetArchiveParams defines parameters for GetArchive. +type GetArchiveParams struct { + // ArchiveType download zip or car files + ArchiveType ArchiveType `form:"archive_type" json:"archive_type"` + + // RefType ref type only allow branch or tag + RefType RefType `form:"refType" json:"refType"` + + // RefName ref(branch/tag) name + RefName string `form:"refName" json:"refName"` +} + // DeleteBranchParams defines parameters for DeleteBranch. type DeleteBranchParams struct { RefName string `form:"refName" json:"refName"` @@ -895,6 +916,9 @@ type ClientInterface interface { UpdateRepository(ctx context.Context, owner string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetArchive request + GetArchive(ctx context.Context, owner string, repository string, params *GetArchiveParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // DeleteBranch request DeleteBranch(ctx context.Context, owner string, repository string, params *DeleteBranchParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -1203,6 +1227,18 @@ func (c *Client) UpdateRepository(ctx context.Context, owner string, repository return c.Client.Do(req) } +func (c *Client) GetArchive(ctx context.Context, owner string, repository string, params *GetArchiveParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetArchiveRequest(c.Server, owner, repository, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) DeleteBranch(ctx context.Context, owner string, repository string, params *DeleteBranchParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewDeleteBranchRequest(c.Server, owner, repository, params) if err != nil { @@ -2567,6 +2603,89 @@ func NewUpdateRepositoryRequestWithBody(server string, owner string, repository return req, nil } +// NewGetArchiveRequest generates requests for GetArchive +func NewGetArchiveRequest(server string, owner string, repository string, params *GetArchiveParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "owner", runtime.ParamLocationPath, owner) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "repository", runtime.ParamLocationPath, repository) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/repos/%s/%s/archive", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "archive_type", runtime.ParamLocationQuery, params.ArchiveType); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refType", runtime.ParamLocationQuery, params.RefType); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "refName", runtime.ParamLocationQuery, params.RefName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewDeleteBranchRequest generates requests for DeleteBranch func NewDeleteBranchRequest(server string, owner string, repository string, params *DeleteBranchParams) (*http.Request, error) { var err error @@ -5183,6 +5302,9 @@ type ClientWithResponsesInterface interface { UpdateRepositoryWithResponse(ctx context.Context, owner string, repository string, body UpdateRepositoryJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateRepositoryResponse, error) + // GetArchiveWithResponse request + GetArchiveWithResponse(ctx context.Context, owner string, repository string, params *GetArchiveParams, reqEditors ...RequestEditorFn) (*GetArchiveResponse, error) + // DeleteBranchWithResponse request DeleteBranchWithResponse(ctx context.Context, owner string, repository string, params *DeleteBranchParams, reqEditors ...RequestEditorFn) (*DeleteBranchResponse, error) @@ -5581,6 +5703,27 @@ func (r UpdateRepositoryResponse) StatusCode() int { return 0 } +type GetArchiveResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r GetArchiveResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetArchiveResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type DeleteBranchResponse struct { Body []byte HTTPResponse *http.Response @@ -6597,6 +6740,15 @@ func (c *ClientWithResponses) UpdateRepositoryWithResponse(ctx context.Context, return ParseUpdateRepositoryResponse(rsp) } +// GetArchiveWithResponse request returning *GetArchiveResponse +func (c *ClientWithResponses) GetArchiveWithResponse(ctx context.Context, owner string, repository string, params *GetArchiveParams, reqEditors ...RequestEditorFn) (*GetArchiveResponse, error) { + rsp, err := c.GetArchive(ctx, owner, repository, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetArchiveResponse(rsp) +} + // DeleteBranchWithResponse request returning *DeleteBranchResponse func (c *ClientWithResponses) DeleteBranchWithResponse(ctx context.Context, owner string, repository string, params *DeleteBranchParams, reqEditors ...RequestEditorFn) (*DeleteBranchResponse, error) { rsp, err := c.DeleteBranch(ctx, owner, repository, params, reqEditors...) @@ -7282,6 +7434,22 @@ func ParseUpdateRepositoryResponse(rsp *http.Response) (*UpdateRepositoryRespons return response, nil } +// ParseGetArchiveResponse parses an HTTP response from a GetArchiveWithResponse call +func ParseGetArchiveResponse(rsp *http.Response) (*GetArchiveResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetArchiveResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + // ParseDeleteBranchResponse parses an HTTP response from a DeleteBranchWithResponse call func ParseDeleteBranchResponse(rsp *http.Response) (*DeleteBranchResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -8283,6 +8451,9 @@ type ServerInterface interface { // update repository // (POST /repos/{owner}/{repository}) UpdateRepository(ctx context.Context, w *JiaozifsResponse, r *http.Request, body UpdateRepositoryJSONRequestBody, owner string, repository string) + // get repo files archive + // (GET /repos/{owner}/{repository}/archive) + GetArchive(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetArchiveParams) // delete branch // (DELETE /repos/{owner}/{repository}/branch) DeleteBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteBranchParams) @@ -8483,6 +8654,12 @@ func (_ Unimplemented) UpdateRepository(ctx context.Context, w *JiaozifsResponse w.WriteHeader(http.StatusNotImplemented) } +// get repo files archive +// (GET /repos/{owner}/{repository}/archive) +func (_ Unimplemented) GetArchive(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params GetArchiveParams) { + w.WriteHeader(http.StatusNotImplemented) +} + // delete branch // (DELETE /repos/{owner}/{repository}/branch) func (_ Unimplemented) DeleteBranch(ctx context.Context, w *JiaozifsResponse, r *http.Request, owner string, repository string, params DeleteBranchParams) { @@ -9567,6 +9744,105 @@ func (siw *ServerInterfaceWrapper) UpdateRepository(w http.ResponseWriter, r *ht handler.ServeHTTP(w, r.WithContext(ctx)) } +// GetArchive operation middleware +func (siw *ServerInterfaceWrapper) GetArchive(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "owner" ------------- + var owner string + + err = runtime.BindStyledParameterWithOptions("simple", "owner", chi.URLParam(r, "owner"), &owner, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + return + } + + // ------------- Path parameter "repository" ------------- + var repository string + + err = runtime.BindStyledParameterWithOptions("simple", "repository", chi.URLParam(r, "repository"), &repository, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "repository", Err: err}) + return + } + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) + + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) + + ctx = context.WithValue(ctx, Cookie_authScopes, []string{}) + + ctx = context.WithValue(ctx, JiaozifsAccessKeyIdScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureMethodScopes, []string{}) + + ctx = context.WithValue(ctx, SignatureVersionScopes, []string{}) + + ctx = context.WithValue(ctx, TimestampScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetArchiveParams + + // ------------- Required query parameter "archive_type" ------------- + + if paramValue := r.URL.Query().Get("archive_type"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "archive_type"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "archive_type", r.URL.Query(), ¶ms.ArchiveType) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "archive_type", Err: err}) + return + } + + // ------------- Required query parameter "refType" ------------- + + if paramValue := r.URL.Query().Get("refType"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refType"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "refType", r.URL.Query(), ¶ms.RefType) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refType", Err: err}) + return + } + + // ------------- Required query parameter "refName" ------------- + + if paramValue := r.URL.Query().Get("refName"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "refName"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "refName", r.URL.Query(), ¶ms.RefName) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "refName", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetArchive(r.Context(), &JiaozifsResponse{w}, r, owner, repository, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // DeleteBranch operation middleware func (siw *ServerInterfaceWrapper) DeleteBranch(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -12257,6 +12533,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/repos/{owner}/{repository}", wrapper.UpdateRepository) }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/repos/{owner}/{repository}/archive", wrapper.GetArchive) + }) r.Group(func(r chi.Router) { r.Delete(options.BaseURL+"/repos/{owner}/{repository}/branch", wrapper.DeleteBranch) }) @@ -12387,105 +12666,107 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "vbz9vwAAAP//4udHdMq7AAA=", + "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", + "AP//ZgXV/l3BAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 58ee746a..1e3b2641 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -267,6 +267,9 @@ components: RefType: type: string enum: ["branch", "wip","tag", "commit"] + ArchiveType: + type: string + enum: [ "zip", "car" ] CreateMergeRequest: type: object required: @@ -1605,6 +1608,7 @@ paths: name: msg description: commit message required: true + allowEmptyValue: true schema: type: string responses: @@ -1656,6 +1660,76 @@ paths: 403: description: Forbidden + /repos/{owner}/{repository}/archive: + parameters: + - in: path + name: owner + required: true + schema: + type: string + - in: path + name: repository + required: true + schema: + type: string + get: + tags: + - repos + operationId: getArchive + summary: get repo files archive + parameters: + - in: query + name: archive_type + description: download zip or car files + required: true + schema: + $ref: "#/components/schemas/ArchiveType" + - in: query + name: refType + description: ref type only allow branch or tag + required: true + schema: + $ref: "#/components/schemas/RefType" + - in: query + name: refName + description: ref(branch/tag) name + required: true + schema: + type: string + responses: + 200: + description: object content + content: + application/octet-stream: + schema: + type: string + format: binary + headers: + Content-Disposition: + schema: + type: string + description: response file + example: attachment; filename="name.pdf" + Content-Length: + schema: + type: integer + format: int64 + Last-Modified: + schema: + type: string + ETag: + schema: + type: string + 401: + description: Unauthorized + 404: + description: object not found + 410: + description: object expired + 416: + description: Requested Range Not Satisfiable + 420: + description: too many requests /repos/{owner}/{repository}/contents: parameters: - in: path diff --git a/auth/aksk/verifier_test.go b/auth/aksk/verifier_test.go index e26662dc..8e019397 100644 --- a/auth/aksk/verifier_test.go +++ b/auth/aksk/verifier_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/GitDataAI/jiaozifs/utils" "github.com/stretchr/testify/require" ) @@ -141,7 +142,7 @@ func (getter skGetter) Get(_ string) (string, error) { func mockHttpRequest() *http.Request { //nolint verbs := []string{"GET", "POST", "PUT", "Delete"} - req, _ := http.NewRequest(verbs[rand.Intn(3)], "http://www.xx.com/index.html", closerWraper{io.LimitReader(crand.Reader, 100)}) + req, _ := http.NewRequest(verbs[rand.Intn(3)], "http://www.xx.com/index.html", utils.CloserWraper{Reader: io.LimitReader(crand.Reader, 100)}) query := req.URL.Query() for i := 0; i < 3; i++ { @@ -151,20 +152,6 @@ func mockHttpRequest() *http.Request { //nolint return req } -var _ io.ReadCloser = (*closerWraper)(nil) - -type closerWraper struct { - reader io.Reader -} - -func (c closerWraper) Read(p []byte) (int, error) { - return c.reader.Read(p) -} - -func (c closerWraper) Close() error { - return nil -} - func randString() string { akBytes, _ := io.ReadAll(io.LimitReader(crand.Reader, 16)) return hex.EncodeToString(akBytes) diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index bde13f83..fa8d34db 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -545,6 +545,73 @@ func (repositoryCtl RepositoryController) ChangeVisible(ctx context.Context, w * w.OK() } +func (repositoryCtl RepositoryController) GetArchive(ctx context.Context, w *api.JiaozifsResponse, _ *http.Request, ownerName string, repositoryName string, params api.GetArchiveParams) { + owner, err := repositoryCtl.Repo.UserRepo().Get(ctx, models.NewGetUserParams().SetName(ownerName)) + if err != nil { + w.Error(err) + return + } + + repository, err := repositoryCtl.Repo.RepositoryRepo().Get(ctx, models.NewGetRepoParams().SetName(repositoryName).SetOwnerID(owner.ID)) + if err != nil { + w.Error(err) + return + } + + if !repositoryCtl.authorizeMember(ctx, w, repository.ID, rbac.Node{ + Permission: rbac.Permission{ + Action: rbacmodel.ReadObjectAction, + Resource: rbacmodel.RepoURArn(owner.ID.String(), repository.ID.String()), + }, + }) { + return + } + + operator, err := auth.GetOperator(ctx) + if err != nil { + w.Error(err) + return + } + + workRepo, err := versionmgr.NewWorkRepositoryFromConfig(ctx, operator, repository, repositoryCtl.Repo, repositoryCtl.PublicStorageConfig) + if err != nil { + w.Error(err) + return + } + + if string(params.RefType) != string(versionmgr.InBranch) && string(params.RefType) != string(versionmgr.InTag) { + w.BadRequest("archive ref type (%s) only allow branch and tag", params.RefType) + return + } + + err = workRepo.CheckOut(ctx, versionmgr.WorkRepoState(params.RefType), params.RefName) + if err != nil { + w.Error(err) + return + } + + readeCloser, size, err := workRepo.Archive(ctx, versionmgr.ArchiveType(params.ArchiveType)) + if err != nil { + w.Error(err) + return + } + defer readeCloser.Close() //nolint + w.Header().Set("Content-Length", fmt.Sprint(size)) + w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, fmt.Sprintf("%s.%s", repository.Name, params.ArchiveType))) + _, err = io.Copy(w, readeCloser) + if err != nil { + objLog.With( + "user", ownerName, + "repo", repositoryName, + "reftype", params.RefType, + "refname", params.RefName, + ).Debugf("archive copy content %v", err) + + } + w.OK() +} + func repositoryToDto(repository *models.Repository) *api.Repository { return &api.Repository{ CreatedAt: repository.CreatedAt.UnixMilli(), diff --git a/go.mod b/go.mod index 603d2226..10a2fc64 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,7 @@ require ( github.com/go-chi/chi/v5 v5.0.10 github.com/go-openapi/swag v0.22.4 github.com/go-test/deep v1.1.0 + github.com/gobwas/glob v0.2.3 github.com/golang-jwt/jwt/v5 v5.2.0 github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.5.0 @@ -37,8 +38,12 @@ require ( github.com/hellofresh/health-go/v5 v5.5.2 github.com/hnlq715/golang-lru v0.4.0 github.com/ipfs/boxo v0.18.0 + github.com/ipfs/go-cid v0.4.1 + github.com/ipfs/go-datastore v0.6.0 + github.com/ipfs/go-ipfs-exchange-offline v0.3.0 github.com/ipfs/go-log/v2 v2.5.1 github.com/ipfs/kubo v0.26.0 + github.com/ipld/go-car v0.5.0 github.com/m1/go-generate-password v0.2.0 github.com/matoous/go-nanoid/v2 v2.0.0 github.com/minio/minio-go/v7 v7.0.64 @@ -119,7 +124,6 @@ require ( github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect @@ -142,18 +146,22 @@ require ( github.com/ipfs/bbloom v0.0.4 // indirect github.com/ipfs/go-bitfield v1.1.0 // indirect github.com/ipfs/go-block-format v0.2.0 // indirect - github.com/ipfs/go-cid v0.4.1 // indirect - github.com/ipfs/go-datastore v0.6.0 // indirect + github.com/ipfs/go-blockservice v0.5.0 // indirect github.com/ipfs/go-ds-measure v0.2.0 // indirect github.com/ipfs/go-fs-lock v0.0.7 // indirect + github.com/ipfs/go-ipfs-blockstore v1.3.0 // indirect github.com/ipfs/go-ipfs-cmds v0.10.0 // indirect + github.com/ipfs/go-ipfs-ds-help v1.1.0 // indirect + github.com/ipfs/go-ipfs-exchange-interface v0.2.0 // indirect github.com/ipfs/go-ipfs-util v0.0.3 // indirect github.com/ipfs/go-ipld-cbor v0.1.0 // indirect github.com/ipfs/go-ipld-format v0.6.0 // indirect github.com/ipfs/go-ipld-legacy v0.2.1 // indirect github.com/ipfs/go-log v1.0.5 // indirect + github.com/ipfs/go-merkledag v0.11.0 // indirect github.com/ipfs/go-metrics-interface v0.0.1 // indirect github.com/ipfs/go-unixfsnode v1.9.0 // indirect + github.com/ipfs/go-verifcid v0.0.2 // indirect github.com/ipld/go-car/v2 v2.13.1 // indirect github.com/ipld/go-codec-dagpb v1.6.0 // indirect github.com/ipld/go-ipld-prime v0.21.0 // indirect @@ -229,6 +237,7 @@ require ( github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc // indirect github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 // indirect github.com/whyrusleeping/cbor-gen v0.0.0-20240109153615-66e95c3e8a87 // indirect + github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f // indirect github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect @@ -238,6 +247,7 @@ require ( go.opentelemetry.io/otel v1.22.0 // indirect go.opentelemetry.io/otel/metric v1.22.0 // indirect go.opentelemetry.io/otel/trace v1.22.0 // indirect + go.uber.org/atomic v1.11.0 // indirect go.uber.org/dig v1.17.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect diff --git a/go.sum b/go.sum index c9810597..7a3ec4d2 100644 --- a/go.sum +++ b/go.sum @@ -400,10 +400,13 @@ github.com/ipfs/boxo v0.18.0 h1:MOL9/AgoV3e7jlVMInicaSdbgralfqSsbkc31dZ9tmw= github.com/ipfs/boxo v0.18.0/go.mod h1:pIZgTWdm3k3pLF9Uq6MB8JEcW07UDwNJjlXW1HELW80= github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA= github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU= +github.com/ipfs/go-bitswap v0.11.0 h1:j1WVvhDX1yhG32NTC9xfxnqycqYIlhzEzLXG/cU1HyQ= +github.com/ipfs/go-bitswap v0.11.0/go.mod h1:05aE8H3XOU+LXpTedeAS0OZpcO1WFsj5niYQH9a1Tmk= github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNiW1Ycs= github.com/ipfs/go-block-format v0.2.0/go.mod h1:+jpL11nFx5A/SPpsoBn6Bzkra/zaArfSmsknbPMYgzM= github.com/ipfs/go-blockservice v0.5.0 h1:B2mwhhhVQl2ntW2EIpaWPwSCxSuqr5fFA93Ms4bYLEY= github.com/ipfs/go-blockservice v0.5.0/go.mod h1:W6brZ5k20AehbmERplmERn8o2Ni3ZZubvAxaIUeaT6w= +github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog= github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= @@ -445,6 +448,8 @@ github.com/ipfs/go-ipfs-pq v0.0.3 h1:YpoHVJB+jzK15mr/xsWC574tyDLkezVrDNeaalQBsTE github.com/ipfs/go-ipfs-pq v0.0.3/go.mod h1:btNw5hsHBpRcSSgZtiNm/SLj5gYIZ18AKtv3kERkRb4= github.com/ipfs/go-ipfs-redirects-file v0.1.1 h1:Io++k0Vf/wK+tfnhEh63Yte1oQK5VGT2hIEYpD0Rzx8= github.com/ipfs/go-ipfs-redirects-file v0.1.1/go.mod h1:tAwRjCV0RjLTjH8DR/AU7VYvfQECg+lpUy2Mdzv7gyk= +github.com/ipfs/go-ipfs-routing v0.3.0 h1:9W/W3N+g+y4ZDeffSgqhgo7BsBSJwPMcyssET9OWevc= +github.com/ipfs/go-ipfs-routing v0.3.0/go.mod h1:dKqtTFIql7e1zYsEuWLyuOU+E0WJWW8JjbTPLParDWo= github.com/ipfs/go-ipfs-util v0.0.2/go.mod h1:CbPtkWJzjLdEcezDns2XYaehFVNXG9zrdrtMecczcsQ= github.com/ipfs/go-ipfs-util v0.0.3 h1:2RFdGez6bu2ZlZdI+rWfIdbQb1KudQp3VGwPtdNCmE0= github.com/ipfs/go-ipfs-util v0.0.3/go.mod h1:LHzG1a0Ig4G+iZ26UUOMjHd+lfM84LZCrn17xAKWBvs= @@ -474,6 +479,8 @@ github.com/ipfs/go-unixfsnode v1.9.0 h1:ubEhQhr22sPAKO2DNsyVBW7YB/zA8Zkif25aBvz8 github.com/ipfs/go-unixfsnode v1.9.0/go.mod h1:HxRu9HYHOjK6HUqFBAi++7DVoWAHn0o4v/nZ/VA+0g8= github.com/ipfs/go-verifcid v0.0.2 h1:XPnUv0XmdH+ZIhLGKg6U2vaPaRDXb9urMyNVCE7uvTs= github.com/ipfs/go-verifcid v0.0.2/go.mod h1:40cD9x1y4OWnFXbLNJYRe7MpNvWlMn3LZAG5Wb4xnPU= +github.com/ipld/go-car v0.5.0 h1:kcCEa3CvYMs0iE5BzD5sV7O2EwMiCIp3uF8tA6APQT8= +github.com/ipld/go-car v0.5.0/go.mod h1:ppiN5GWpjOZU9PgpAZ9HbZd9ZgSpwPMr48fGRJOWmvE= github.com/ipld/go-car/v2 v2.13.1 h1:KnlrKvEPEzr5IZHKTXLAEub+tPrzeAFQVRlSQvuxBO4= github.com/ipld/go-car/v2 v2.13.1/go.mod h1:QkdjjFNGit2GIkpQ953KBwowuoukoM75nP/JI1iDJdo= github.com/ipld/go-codec-dagpb v1.6.0 h1:9nYazfyu9B1p3NAgfVdpRco3Fs2nFC72DqVsMj6rOcc= @@ -633,6 +640,7 @@ github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2 github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk= github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= +github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= diff --git a/integrationtest/repo_test.go b/integrationtest/repo_test.go index aa0d7cb7..e3130cb7 100644 --- a/integrationtest/repo_test.go +++ b/integrationtest/repo_test.go @@ -3,6 +3,7 @@ package integrationtest import ( "context" "net/http" + "strconv" "github.com/GitDataAI/jiaozifs/api" apiimpl "github.com/GitDataAI/jiaozifs/api/api_impl" @@ -23,7 +24,6 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { _ = createUser(ctx, client, userName) loginAndSwitch(ctx, client, userName, false) - }) c.Convey("create repo", func(c convey.C) { c.Convey("forbidden create repo name", func() { @@ -493,5 +493,129 @@ func RepoSpec(ctx context.Context, urlStr string) func(c convey.C) { convey.So(getResp.StatusCode, convey.ShouldEqual, http.StatusNotFound) }) }) + + c.Convey("get archive file", func(c convey.C) { + userName2 := "testarchive" + repo2Name := "archiverepo" + refName := "testbranch" + tag := "vt0.0.1" + c.Convey("init", func() { + loginAndSwitch(ctx, client, "admin2", true) + _ = createUser(ctx, client, userName2) + loginAndSwitch(ctx, client, userName2, false) + _ = createRepo(ctx, client, repo2Name, false) + _ = createBranch(ctx, client, userName2, repo2Name, "main", refName) + _ = createWip(ctx, client, userName2, repo2Name, refName) + _ = uploadObject(ctx, client, userName2, repo2Name, refName, "g.txt", true) + _ = uploadObject(ctx, client, userName2, repo2Name, refName, "a/b.txt", true) + _ = uploadObject(ctx, client, userName2, repo2Name, refName, "b/b.txt", true) + _ = uploadObject(ctx, client, userName2, repo2Name, refName, "c/b.txt", true) + _ = commitWip(ctx, client, userName2, repo2Name, refName, "aaa") + _ = createTag(ctx, client, userName2, repo2Name, tag, refName) + }) + c.Convey("no auth", func() { + re := client.RequestEditors + client.RequestEditors = nil + resp, err := client.GetArchive(ctx, userName2, repo2Name, &api.GetArchiveParams{ + ArchiveType: api.Zip, + RefName: refName, + RefType: api.RefTypeBranch, + }) + client.RequestEditors = re + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + c.Convey("get archive in not exit repo", func() { + resp, err := client.GetArchive(ctx, userName2, "happyrunfake", &api.GetArchiveParams{ + ArchiveType: api.Zip, + RefName: refName, + RefType: api.RefTypeBranch, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("get archive in non exit user", func() { + resp, err := client.GetArchive(ctx, "telo", repo2Name, &api.GetArchiveParams{ + ArchiveType: api.Zip, + RefName: refName, + RefType: api.RefTypeBranch, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("get archive in non exit ref", func() { + resp, err := client.GetArchive(ctx, userName2, repo2Name, &api.GetArchiveParams{ + ArchiveType: api.Zip, + RefName: "gggg", + RefType: api.RefTypeBranch, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusNotFound) + }) + + c.Convey("get archive in non other repo", func() { + resp, err := client.GetArchive(ctx, "jimmy", "happygo", &api.GetArchiveParams{ + ArchiveType: api.Zip, + RefName: "main", + RefType: api.RefTypeBranch, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusUnauthorized) + }) + + c.Convey("success get zip archive in branch", func() { + resp, err := client.GetArchive(ctx, userName2, repo2Name, &api.GetArchiveParams{ + ArchiveType: api.Zip, + RefName: refName, + RefType: api.RefTypeBranch, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseGetArchiveResponse(resp) + convey.So(err, convey.ShouldBeNil) + sizeStr := resp.Header.Get("Content-Length") + size, err := strconv.Atoi(sizeStr) + convey.So(err, convey.ShouldBeNil) + convey.ShouldHaveLength(size, result.Body) + }) + + c.Convey("success get car archive in branch", func() { + resp, err := client.GetArchive(ctx, userName2, repo2Name, &api.GetArchiveParams{ + ArchiveType: api.Car, + RefName: refName, + RefType: api.RefTypeBranch, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseGetArchiveResponse(resp) + convey.So(err, convey.ShouldBeNil) + sizeStr := resp.Header.Get("Content-Length") + size, err := strconv.Atoi(sizeStr) + convey.So(err, convey.ShouldBeNil) + convey.ShouldHaveLength(size, result.Body) + }) + + c.Convey("success get zip archive in tag", func() { + resp, err := client.GetArchive(ctx, userName2, repo2Name, &api.GetArchiveParams{ + ArchiveType: api.Zip, + RefName: tag, + RefType: api.RefTypeTag, + }) + convey.So(err, convey.ShouldBeNil) + convey.So(resp.StatusCode, convey.ShouldEqual, http.StatusOK) + + result, err := api.ParseGetArchiveResponse(resp) + convey.So(err, convey.ShouldBeNil) + sizeStr := resp.Header.Get("Content-Length") + size, err := strconv.Atoi(sizeStr) + convey.So(err, convey.ShouldBeNil) + convey.ShouldHaveLength(size, result.Body) + }) + + }) } } diff --git a/utils/io.go b/utils/io.go new file mode 100644 index 00000000..565a25de --- /dev/null +++ b/utils/io.go @@ -0,0 +1,17 @@ +package utils + +import "io" + +var _ io.ReadCloser = (*CloserWraper)(nil) + +type CloserWraper struct { + Reader io.Reader +} + +func (c CloserWraper) Read(p []byte) (int, error) { + return c.Reader.Read(p) +} + +func (c CloserWraper) Close() error { + return nil +} diff --git a/versionmgr/archive_repo.go b/versionmgr/archive_repo.go new file mode 100644 index 00000000..26d787b2 --- /dev/null +++ b/versionmgr/archive_repo.go @@ -0,0 +1,169 @@ +package versionmgr + +import ( + "archive/zip" + "context" + "errors" + "fmt" + "io" + "os" + path2 "path" + "path/filepath" + "strings" + + chunker "github.com/ipfs/boxo/chunker" + + "github.com/ipfs/go-cid" + + "github.com/GitDataAI/jiaozifs/models" + bserv "github.com/ipfs/boxo/blockservice" + bstore "github.com/ipfs/boxo/blockstore" + dag "github.com/ipfs/boxo/ipld/merkledag" + ft "github.com/ipfs/boxo/ipld/unixfs" + "github.com/ipfs/boxo/mfs" + ds "github.com/ipfs/go-datastore" + dssync "github.com/ipfs/go-datastore/sync" + offline "github.com/ipfs/go-ipfs-exchange-offline" + car "github.com/ipld/go-car" + + importer "github.com/ipfs/boxo/ipld/unixfs/importer" +) + +type RepoArchiver struct { + rootPath string + walker IWalk + getReader func(context.Context, *models.Blob, string) (io.ReadCloser, error) +} + +func NewRepoArchiver(rootPath string, walker IWalk, getReader func(context.Context, *models.Blob, string) (io.ReadCloser, error)) *RepoArchiver { + return &RepoArchiver{rootPath: rootPath, walker: walker, getReader: getReader} +} + +func (repo *RepoArchiver) ArchiveZip(ctx context.Context, dest string) error { + zipFile, err := os.Create(dest) + if err != nil { + return err + } + defer zipFile.Close() //nolint:errcheck + + zipWriter := zip.NewWriter(zipFile) + defer zipWriter.Close() //nolint:errcheck + + _, err = zipWriter.CreateHeader(&zip.FileHeader{ + Name: repo.rootPath + "/", + }) + if err != nil { + return err + } + + return repo.walker.Walk(ctx, func(entry *models.TreeEntry, blob *models.Blob, path string) error { + if entry.IsDir { + path = fmt.Sprintf("%s%c", path, os.PathSeparator) + _, err = zipWriter.CreateHeader(&zip.FileHeader{ + Name: path2.Join(repo.rootPath, path) + "/", + }) + return err + } + + reader, err := repo.getReader(ctx, blob, path) + if err != nil { + return err + } + defer reader.Close() //nolint + + f, err := zipWriter.Create(path2.Join(repo.rootPath, path)) + if err != nil { + return err + } + + _, err = io.Copy(f, reader) + return err + }) +} + +func (repo *RepoArchiver) ArchiveCar(ctx context.Context, dest string) error { + db := dssync.MutexWrap(ds.NewMapDatastore()) //todo use disk to cache data + bs := bstore.NewBlockstore(db) + blockSrv := bserv.New(bs, offline.Exchange(bs)) + dagSrv := dag.NewDAGService(blockSrv) + rootNode := dag.NodeWithData(ft.FolderPBData()) + + root, err := mfs.NewRoot(ctx, dagSrv, rootNode, nil) + if err != nil { + return err + } + defer root.Close() //nolint:errcheck + + rootDir := root.GetDirectory() + err = repo.walker.Walk(ctx, func(entry *models.TreeEntry, blob *models.Blob, treePath string) error { + path := path2.Join(repo.rootPath, treePath) + if entry.IsDir { + _, err = mkdirP(rootDir, path) + return err + } + + reader, err := repo.getReader(ctx, blob, treePath) + if err != nil { + return err + } + defer reader.Close() //nolint + + dir := filepath.Dir(path) + base := filepath.Base(path) + dirNd, err := mfs.Lookup(root, dir) + if err != nil { + return err + } + + nd, err := importer.BuildDagFromReader(dagSrv, chunker.DefaultSplitter(reader)) + if err != nil { + return err + } + return dirNd.(*mfs.Directory).AddChild(base, nd) + }) + if err != nil { + return err + } + err = root.Flush() + if err != nil { + return err + } + + ipldNode, err := rootDir.GetNode() + if err != nil { + return err + } + + carFile, err := os.Create(dest) + if err != nil { + return err + } + defer carFile.Close() //nolint:errcheck + return car.WriteCar(ctx, dagSrv, []cid.Cid{ipldNode.Cid()}, carFile) +} + +func mkdirP(root *mfs.Directory, pth string) (*mfs.Directory, error) { + dirs := strings.Split(pth, "/") + cur := root + for _, d := range dirs { + n, err := cur.Mkdir(d) + if err != nil && !errors.Is(err, os.ErrExist) { + return nil, err + } + if errors.Is(err, os.ErrExist) { + fsn, err := cur.Child(d) + if err != nil { + return nil, err + } + switch fsn := fsn.(type) { + case *mfs.Directory: + n = fsn + case *mfs.File: + return nil, errors.New("find file") + } + } + + cur = n + } + return cur, nil +} diff --git a/versionmgr/archive_repo_test.go b/versionmgr/archive_repo_test.go new file mode 100644 index 00000000..3aa018b2 --- /dev/null +++ b/versionmgr/archive_repo_test.go @@ -0,0 +1,176 @@ +package versionmgr + +import ( + "bytes" + "context" + "fmt" + "io" + "os" + "path" + "testing" + + bserv "github.com/ipfs/boxo/blockservice" + bstore "github.com/ipfs/boxo/blockstore" + dag "github.com/ipfs/boxo/ipld/merkledag" + "github.com/ipfs/boxo/mfs" + ds "github.com/ipfs/go-datastore" + dssync "github.com/ipfs/go-datastore/sync" + offline "github.com/ipfs/go-ipfs-exchange-offline" + + "github.com/ipld/go-car" + + "github.com/stretchr/testify/require" + + "github.com/GitDataAI/jiaozifs/utils" + + "github.com/GitDataAI/jiaozifs/models" +) + +type mockWalker struct { + dirs []string + files map[string][]byte +} + +func (wk mockWalker) Walk(_ context.Context, fn func(entry *models.TreeEntry, blob *models.Blob, path string) error) error { + for _, dir := range wk.dirs { + err := fn(&models.TreeEntry{ + IsDir: true, + }, nil, dir) + if err != nil { + return err + } + } + for path, data := range wk.files { + err := fn(&models.TreeEntry{ + IsDir: false, + }, &models.Blob{ + Size: int64(len(data)), + }, path) + if err != nil { + return err + } + } + return nil +} + +func TestRepoArchiver_ArchiveZip(t *testing.T) { + ctx := context.Background() + wk := &mockWalker{ + dirs: []string{ + "a", + "a/b", + "m", + }, + files: map[string][]byte{ + "1.txt": []byte("111111111111111111111111"), + "a/2.txt": []byte("222222222222222222222222"), + "a/3.txt": []byte("3333333333333333333333333"), + "a/b/4.txt": []byte("4444444444444444444444444444"), + "m/5.txt": []byte("555555555555555555555"), + }, + } + archiver := NewRepoArchiver( + "testdir", + wk, + func(ctx context.Context, _ *models.Blob, s string) (io.ReadCloser, error) { + data, ok := wk.files[s] + if !ok { + return nil, fmt.Errorf("data not found %s", s) + } + return utils.CloserWraper{Reader: bytes.NewReader(data)}, nil + }, + ) + + tmpDir, err := os.MkdirTemp(os.TempDir(), "*") + require.NoError(t, err) + tmpFile := path.Join(tmpDir, "test.zip") + + err = archiver.ArchiveZip(ctx, tmpFile) + require.NoError(t, err) + + fmt.Println(tmpFile) +} + +func TestRepoArchiver_ArchiveCar(t *testing.T) { + ctx := context.Background() + wk := &mockWalker{ + dirs: []string{ + "a", + "a/b", + "m", + }, + files: map[string][]byte{ + "1.txt": []byte("111111111111111111111111"), + "a/2.txt": []byte("222222222222222222222222"), + "a/3.txt": []byte("3333333333333333333333333"), + "a/b/4.txt": []byte("4444444444444444444444444444"), + "m/5.txt": []byte("555555555555555555555"), + }, + } + archiver := NewRepoArchiver( + "testdir", + wk, + func(ctx context.Context, _ *models.Blob, s string) (io.ReadCloser, error) { + data, ok := wk.files[s] + if !ok { + return nil, fmt.Errorf("data not found %s", s) + } + return utils.CloserWraper{Reader: bytes.NewReader(data)}, nil + }, + ) + + tmpDir, err := os.MkdirTemp(os.TempDir(), "*") + require.NoError(t, err) + tmpFile := path.Join(tmpDir, "test.car") + + err = archiver.ArchiveCar(ctx, tmpFile) + require.NoError(t, err) + + //check data in car + fs, err := os.Open(tmpFile) + require.NoError(t, err) + + db := dssync.MutexWrap(ds.NewMapDatastore()) + bs := bstore.NewBlockstore(db) + header, err := car.LoadCar(ctx, bs, fs) + require.NoError(t, err) + blockserv := bserv.New(bs, offline.Exchange(bs)) + dagSrv := dag.NewDAGService(blockserv) + + rootNode, err := dagSrv.Get(ctx, header.Roots[0]) + require.NoError(t, err) + rd, err := dag.DecodeProtobuf(rootNode.RawData()) + require.NoError(t, err) + + root, err := mfs.NewRoot(ctx, dagSrv, rd, nil) + require.NoError(t, err) + for file, data := range wk.files { + actualData, err := readFile(root, path.Join(archiver.rootPath, file), 0) + require.NoError(t, err) + require.Equal(t, data, actualData) + } +} + +func readFile(rt *mfs.Root, path string, offset int64) ([]byte, error) { + n, err := mfs.Lookup(rt, path) + if err != nil { + return nil, err + } + + fi, ok := n.(*mfs.File) + if !ok { + return nil, fmt.Errorf("%s was not a file", path) + } + + fd, err := fi.Open(mfs.Flags{Read: true}) + if err != nil { + return nil, err + } + + _, err = fd.Seek(offset, io.SeekStart) + if err != nil { + return nil, err + } + defer fd.Close() //nolint:errcheck + return io.ReadAll(fd) +} diff --git a/versionmgr/files_walk.go b/versionmgr/files_walk.go index dccb8f81..87fb74f5 100644 --- a/versionmgr/files_walk.go +++ b/versionmgr/files_walk.go @@ -11,17 +11,25 @@ import ( var ErrHalt = errors.New("halt walk") +type IWalk interface { + Walk(ctx context.Context, fn func(entry *models.TreeEntry, blob *models.Blob, path string) error) error +} + type FileWalk struct { object models.IFileTreeRepo curNode *TreeNode } +func NewFileWalk(object models.IFileTreeRepo, curNode *TreeNode) *FileWalk { + return &FileWalk{object: object, curNode: curNode} +} + type nodeWithPath struct { curNode *TreeNode path string } -func (wk FileWalk) Walk(ctx context.Context, fn func(blob *models.Blob, path string) error) error { +func (wk FileWalk) Walk(ctx context.Context, fn func(entry *models.TreeEntry, blob *models.Blob, path string) error) error { cache := list.New() cache.PushFront(nodeWithPath{wk.curNode, ""}) for { @@ -41,10 +49,13 @@ func (wk FileWalk) Walk(ctx context.Context, fn func(blob *models.Blob, path str } cache.PushFront(nodeWithPath{treeNode, path.Join(curNode.path, treeNode.Name())}) - continue } for i := 0; i < len(subNodes); i++ { if subNodes[i].IsDir { + err := fn(&subNodes[i], nil, path.Join(curNode.path, subNodes[i].Name)) + if err != nil { + return err + } continue } @@ -53,7 +64,7 @@ func (wk FileWalk) Walk(ctx context.Context, fn func(blob *models.Blob, path str return err } - err = fn(blob, path.Join(curNode.path, subNodes[i].Name)) + err = fn(&subNodes[i], blob, path.Join(curNode.path, subNodes[i].Name)) if err != nil { return err } diff --git a/versionmgr/files_walk_test.go b/versionmgr/files_walk_test.go index 52f38460..aef2074f 100644 --- a/versionmgr/files_walk_test.go +++ b/versionmgr/files_walk_test.go @@ -2,7 +2,6 @@ package versionmgr import ( "context" - "fmt" "testing" "github.com/GitDataAI/jiaozifs/models" @@ -36,21 +35,31 @@ func TestFileWalk_Walk(t *testing.T) { object: objRepo, curNode: workTree.root, } - var paths []string - err = wk.Walk(ctx, func(_ *models.Blob, path string) error { - fmt.Println(path) - paths = append(paths, path) + var filePath []string + var dirPaths []string + err = wk.Walk(ctx, func(entry *models.TreeEntry, _ *models.Blob, path string) error { + if entry.IsDir { + dirPaths = append(dirPaths, path) + } else { + filePath = append(filePath, path) + } return nil }) require.NoError(t, err) - require.Equal(t, "a.txt", paths[0]) - require.Equal(t, "b.txt", paths[1]) - require.Equal(t, "a/b/c.txt", paths[2]) - require.Equal(t, "a/b/d.txt", paths[3]) - require.Equal(t, "a/c/e.txt", paths[4]) - require.Equal(t, "a/c/f.txt", paths[5]) - require.Equal(t, "mm/f.txt", paths[6]) - require.Equal(t, "mm/c/f.txt", paths[7]) + require.Equal(t, "a", dirPaths[0]) + require.Equal(t, "mm", dirPaths[1]) + require.Equal(t, "a/b", dirPaths[2]) + require.Equal(t, "a/c", dirPaths[3]) + require.Equal(t, "mm/c", dirPaths[4]) + + require.Equal(t, "a.txt", filePath[0]) + require.Equal(t, "b.txt", filePath[1]) + require.Equal(t, "a/b/c.txt", filePath[2]) + require.Equal(t, "a/b/d.txt", filePath[3]) + require.Equal(t, "a/c/e.txt", filePath[4]) + require.Equal(t, "a/c/f.txt", filePath[5]) + require.Equal(t, "mm/f.txt", filePath[6]) + require.Equal(t, "mm/c/f.txt", filePath[7]) } func addLeaves(ctx context.Context, t *testing.T, workTree *WorkTree, repoID uuid.UUID, path string) { diff --git a/versionmgr/work_repo.go b/versionmgr/work_repo.go index ce06c13c..40e041e0 100644 --- a/versionmgr/work_repo.go +++ b/versionmgr/work_repo.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "os" + "path" "time" "github.com/GitDataAI/jiaozifs/versionmgr/merkletrie" @@ -779,6 +780,54 @@ func (repository *WorkRepository) Merge(ctx context.Context, toMergeCommitHash h return newCommit, nil } +type ArchiveType string + +const ( + ZipArchiveType ArchiveType = "zip" + CarArchiveType ArchiveType = "car" +) + +func (repository *WorkRepository) Archive(ctx context.Context, archiveType ArchiveType) (io.ReadCloser, int64, error) { + rootTree, err := repository.RootTree(ctx) + if err != nil { + return nil, 0, err + } + + wk := NewFileWalk(rootTree.object, rootTree.root) + reader := func(ctx context.Context, blob *models.Blob, s string) (io.ReadCloser, error) { + return repository.ReadBlob(ctx, blob, nil) + } + + archiver := NewRepoArchiver(repository.repoModel.Name, wk, reader) + tmpDir, err := os.MkdirTemp(os.TempDir(), "*") //todo file cache for archive + if err != nil { + return nil, 0, err + } + var tmpFile string + switch archiveType { + case ZipArchiveType: + tmpFile = path.Join(tmpDir, hash.Hash(rootTree.root.Hash()).Hex()+".zip") + err = archiver.ArchiveZip(ctx, tmpFile) + case CarArchiveType: + tmpFile = path.Join(tmpDir, hash.Hash(rootTree.root.Hash()).Hex()+".car") + err = archiver.ArchiveZip(ctx, tmpFile) + default: + return nil, 0, fmt.Errorf("unexpect archive type %s", archiveType) + } + if err != nil { + return nil, 0, err + } + st, err := os.Stat(tmpFile) + if err != nil { + return nil, 0, err + } + fs, err := os.Open(tmpFile) + if err != nil { + return nil, 0, err + } + return fs, st.Size(), nil +} + func (repository *WorkRepository) setCurState(state WorkRepoState, wip *models.WorkingInProcess, branch *models.Branch, tag *models.Tag, commit *models.Commit) { repository.state = state repository.wip = wip diff --git a/versionmgr/work_repo_test.go b/versionmgr/work_repo_test.go index 224823f1..57a1b216 100644 --- a/versionmgr/work_repo_test.go +++ b/versionmgr/work_repo_test.go @@ -15,7 +15,6 @@ import ( "github.com/GitDataAI/jiaozifs/block/mem" "github.com/GitDataAI/jiaozifs/config" "github.com/GitDataAI/jiaozifs/models" - "github.com/GitDataAI/jiaozifs/models/filemode" "github.com/GitDataAI/jiaozifs/testhelper" "github.com/GitDataAI/jiaozifs/utils" "github.com/GitDataAI/jiaozifs/utils/hash" @@ -260,7 +259,7 @@ func TestWorkRepositoryRevert(t *testing.T) { err = workRepo.CheckOut(ctx, InBranch, "main") require.NoError(t, err) err = workRepo.ChangeInWip(ctx, func(workTree *WorkTree) error { - return appendChangeToWorkTree(ctx, workTree, testData2) + return appendChangeToWorkTree(ctx, workRepo, workTree, testData2) }) require.Error(t, err) //state not correct @@ -268,7 +267,7 @@ func TestWorkRepositoryRevert(t *testing.T) { require.NoError(t, err) err = workRepo.ChangeInWip(ctx, func(workTree *WorkTree) error { - return appendChangeToWorkTree(ctx, workTree, testData2) + return appendChangeToWorkTree(ctx, workRepo, workTree, testData2) }) require.NoError(t, err) @@ -300,7 +299,7 @@ func TestWorkRepositoryRevert(t *testing.T) { err = workRepo.CheckOut(ctx, InBranch, "main") require.NoError(t, err) err = workRepo.ChangeInWip(ctx, func(workTree *WorkTree) error { - return appendChangeToWorkTree(ctx, workTree, testData2) + return appendChangeToWorkTree(ctx, workRepo, workTree, testData2) }) require.Error(t, err) //state not correct @@ -308,7 +307,7 @@ func TestWorkRepositoryRevert(t *testing.T) { require.NoError(t, err) err = workRepo.ChangeInWip(ctx, func(workTree *WorkTree) error { - return appendChangeToWorkTree(ctx, workTree, testData2) + return appendChangeToWorkTree(ctx, workRepo, workTree, testData2) }) require.NoError(t, err) @@ -337,7 +336,7 @@ func TestWorkRepositoryRevert(t *testing.T) { err = workRepo.CheckOut(ctx, InBranch, "main") require.NoError(t, err) err = workRepo.ChangeInWip(ctx, func(workTree *WorkTree) error { - return appendChangeToWorkTree(ctx, workTree, testData2) + return appendChangeToWorkTree(ctx, workRepo, workTree, testData2) }) require.Error(t, err) //state not correct @@ -345,7 +344,7 @@ func TestWorkRepositoryRevert(t *testing.T) { require.NoError(t, err) err = workRepo.ChangeInWip(ctx, func(workTree *WorkTree) error { - return appendChangeToWorkTree(ctx, workTree, testData2) + return appendChangeToWorkTree(ctx, workRepo, workTree, testData2) }) require.NoError(t, err) @@ -572,7 +571,58 @@ func TestWorkRepositoryCreateTag(t *testing.T) { _, err = workRepo.CreateTag(ctx, "v0.0.1", nil) require.Error(t, err) } +func TestWorkRepository_Archive(t *testing.T) { + ctx := context.Background() + closeDB, _, db := testhelper.SetupDatabase(ctx, t) + defer closeDB() + repo := models.NewRepo(db) + + user, err := makeUser(ctx, repo.UserRepo(), "admin") + require.NoError(t, err) + + project, err := makeRepository(ctx, repo, user, t.Name()) + require.NoError(t, err) + adapter := mem.New(ctx) + workRepo := NewWorkRepositoryFromAdapter(ctx, user, project, repo, adapter) + + reader, _, err := workRepo.Archive(ctx, ZipArchiveType) + defer reader.Close() //nolint + require.NoError(t, err) + + testData1 := ` +1|a.txt |aaaaaaa +` + _, err = addChangesToWip(ctx, workRepo, "main", "", testData1) + require.NoError(t, err) + + err = workRepo.CheckOut(ctx, InBranch, "main") + require.NoError(t, err) + + { + //unexpect type + _, _, err = workRepo.Archive(ctx, "gz") + require.Error(t, err) + } + + { + //test zip + reader, _, err := workRepo.Archive(ctx, ZipArchiveType) + require.NoError(t, err) + defer reader.Close() //nolint + _, err = io.ReadAll(reader) + require.NoError(t, err) + } + + { + //test car + reader, _, err := workRepo.Archive(ctx, CarArchiveType) + require.NoError(t, err) + defer reader.Close() //nolint + _, err = io.ReadAll(reader) + require.NoError(t, err) + } +} func makeUser(ctx context.Context, userRepo models.IUserRepo, name string) (*models.User, error) { user := &models.User{ Name: name, @@ -675,30 +725,23 @@ func addChangesToWip(ctx context.Context, workRepo *WorkRepository, branchName s } return workRepo.ChangeAndCommit(ctx, msg, func(workTree *WorkTree) error { - return appendChangeToWorkTree(ctx, workTree, testData) + return appendChangeToWorkTree(ctx, workRepo, workTree, testData) }) } -func appendChangeToWorkTree(ctx context.Context, workTree *WorkTree, testData string) error { +func appendChangeToWorkTree(ctx context.Context, workRepo *WorkRepository, workTree *WorkTree, testData string) error { lines := strings.Split(testData, "\n") - var err error for _, line := range lines { if len(strings.TrimSpace(line)) == 0 { continue } commitData := strings.Split(strings.TrimSpace(line), "|") fullPath := strings.TrimSpace(commitData[1]) - fileHash := strings.TrimSpace(commitData[2]) - blob := &models.Blob{ - Hash: hash.Hash(fileHash), - RepositoryID: workTree.RepositoryID(), - Type: models.BlobObject, - Size: 10, - Properties: models.Property{Mode: filemode.Regular}, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), + fileContent := []byte(strings.TrimSpace(commitData[2])) + blob, err := workRepo.WriteBlob(ctx, bytes.NewReader(fileContent), int64(len(fileContent)), models.Property{}) + if err != nil { + return err } - if commitData[0] == "1" { err = workTree.AddLeaf(ctx, fullPath, blob) if err != nil { diff --git a/versionmgr/worktree.go b/versionmgr/worktree.go index 60e96004..05022f2c 100644 --- a/versionmgr/worktree.go +++ b/versionmgr/worktree.go @@ -459,8 +459,10 @@ func (workTree *WorkTree) GetTreeManifest(ctx context.Context, pattern string) ( files := make([]string, 0) var size int64 - err = wk.Walk(ctx, func(blob *models.Blob, path string) error { - fmt.Println(path) + err = wk.Walk(ctx, func(entry *models.TreeEntry, blob *models.Blob, path string) error { + if entry.IsDir { + return nil + } if g.Match(path) { size += blob.Size files = append(files, path) From c9ccebf2e441d5a19d61f8ff8479d5a4225646ca Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Sun, 24 Mar 2024 15:45:53 +0800 Subject: [PATCH 205/210] feat: add command to create ak sk (#164) --- api/aksk_opts.go | 10 ++++++++++ cmd/aksk.go | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ cmd/helper.go | 9 ++++++++- cmd/root.go | 11 ++++++++--- 4 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 cmd/aksk.go diff --git a/api/aksk_opts.go b/api/aksk_opts.go index 2b77be9a..ecdb0669 100644 --- a/api/aksk_opts.go +++ b/api/aksk_opts.go @@ -16,3 +16,13 @@ func AkSkOption(ak, sk string) ClientOption { return nil } } + +func UPOption(user, password string) ClientOption { + return func(client *Client) error { + client.RequestEditors = append(client.RequestEditors, func(_ context.Context, req *http.Request) error { + req.SetBasicAuth(user, password) + return nil + }) + return nil + } +} diff --git a/cmd/aksk.go b/cmd/aksk.go new file mode 100644 index 00000000..7d0f3d7d --- /dev/null +++ b/cmd/aksk.go @@ -0,0 +1,49 @@ +package cmd + +import ( + "fmt" + + "github.com/GitDataAI/jiaozifs/api" + "github.com/GitDataAI/jiaozifs/utils" + "github.com/spf13/cobra" +) + +// versionCmd represents the version command +var akskCmd = &cobra.Command{ + Use: "aksk", + Short: "ak sk command", +} + +var createAkskCmd = &cobra.Command{ + Use: "create", + Short: "create ak/sk", + RunE: func(cmd *cobra.Command, _ []string) error { + client, err := GetClient(cmd) + if err != nil { + return err + } + + desc := cmd.Flags().Lookup("description").Value.String() + resp, err := client.CreateAksk(cmd.Context(), &api.CreateAkskParams{ + Description: utils.String(desc), + }) + if err != nil { + return err + } + + result, err := api.ParseCreateAkskResponse(resp) + if err != nil { + return err + } + + fmt.Printf("ak %s sk %s \n", result.JSON201.AccessKey, result.JSON201.SecretKey) + return nil + }, +} + +func init() { + rootCmd.AddCommand(akskCmd) + + akskCmd.AddCommand(createAkskCmd) + createAkskCmd.Flags().String("description", "", "description") +} diff --git a/cmd/helper.go b/cmd/helper.go index 6f46f0a3..d36fa97e 100644 --- a/cmd/helper.go +++ b/cmd/helper.go @@ -12,7 +12,14 @@ func GetClient(cmd *cobra.Command) (*api.Client, error) { url := cmd.Flags().Lookup("url").Value.String() ak := cmd.Flags().Lookup("ak").Value.String() sk := cmd.Flags().Lookup("sk").Value.String() - return api.NewClient(url, api.AkSkOption(ak, sk)) + + user := cmd.Flags().Lookup("user").Value.String() + password := cmd.Flags().Lookup("password").Value.String() + + if len(ak) > 0 { + return api.NewClient(url, api.AkSkOption(ak, sk)) + } + return api.NewClient(url, api.UPOption(user, password)) } func tryLogError(resp *http.Response) string { diff --git a/cmd/root.go b/cmd/root.go index 35ef80fb..79743591 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -36,7 +36,12 @@ func init() { _ = viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config")) _ = viper.BindPFlag("log.level", rootCmd.PersistentFlags().Lookup("log-level")) - uploadCmd.PersistentFlags().String("url", "https://127.0.0.1:34913", "url") - uploadCmd.PersistentFlags().String("ak", "", "access key") - uploadCmd.PersistentFlags().String("sk", "", "secret key") + rootCmd.PersistentFlags().String("ak", "", "access key") + rootCmd.PersistentFlags().String("sk", "", "secret key") + + rootCmd.PersistentFlags().String("user", "", "user name") + rootCmd.PersistentFlags().String("password", "", "password") + + rootCmd.PersistentFlags().String("url", "https://127.0.0.1:34913", "url") + } From d6effcdad35be1fc363deb7a1b71c8a1b33cbbbf Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Sun, 24 Mar 2024 16:07:26 +0800 Subject: [PATCH 206/210] chores: example for ml (#165) --- .fend.yaml | 1 + examples/face_dect.ipynb | 877 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 878 insertions(+) create mode 100644 examples/face_dect.ipynb diff --git a/.fend.yaml b/.fend.yaml index 0bde45fc..0ebf48b1 100644 --- a/.fend.yaml +++ b/.fend.yaml @@ -9,3 +9,4 @@ skip: - .png - .output - .jpg + - .ipynb diff --git a/examples/face_dect.ipynb b/examples/face_dect.ipynb new file mode 100644 index 00000000..ec9d861b --- /dev/null +++ b/examples/face_dect.ipynb @@ -0,0 +1,877 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "8xHff6nf0WUr" + }, + "source": [ + "# Face detection and recognition inference pipeline\n", + "\n", + "The following example illustrates how to use the `facenet_pytorch` python package to perform face detection and recogition on an image dataset using an Inception Resnet V1 pretrained on the VGGFace2 dataset.\n", + "\n", + "The following Pytorch methods are included:\n", + "* Datasets\n", + "* Dataloaders\n", + "* GPU/CPU processing" + ] + }, + { + "cell_type": "code", + "source": [ + "!apt-get install python3.12\n", + "!pip install git+https://github.com/GitDataAI/jiaozifs_client_py.git\n", + "!pip install torchvision\n", + "!pip install Pillow\n", + "!pip install numpy\n", + "!pip install facenet_pytorch\n", + "!pip install git+https://github.com/GitDataAI/jz_dataloader.git" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "pgsA56KD2wzv", + "outputId": "85506cb4-9ca5-4cff-c601-02021deede8b" + }, + "execution_count": 1, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Reading package lists... Done\n", + "Building dependency tree... Done\n", + "Reading state information... Done\n", + "The following additional packages will be installed:\n", + " libpython3.12-minimal libpython3.12-stdlib mailcap mime-support python3.12-minimal\n", + "Suggested packages:\n", + " python3.12-venv binfmt-support\n", + "The following NEW packages will be installed:\n", + " libpython3.12-minimal libpython3.12-stdlib mailcap mime-support python3.12 python3.12-minimal\n", + "0 upgraded, 6 newly installed, 0 to remove and 39 not upgraded.\n", + "Need to get 6,150 kB of archives.\n", + "After this operation, 23.3 MB of additional disk space will be used.\n", + "Get:1 http://archive.ubuntu.com/ubuntu jammy/main amd64 mailcap all 3.70+nmu1ubuntu1 [23.8 kB]\n", + "Get:2 http://archive.ubuntu.com/ubuntu jammy/main amd64 mime-support all 3.66 [3,696 B]\n", + "Get:3 https://ppa.launchpadcontent.net/deadsnakes/ppa/ubuntu jammy/main amd64 libpython3.12-minimal amd64 3.12.2-1+jammy3 [874 kB]\n", + "Get:4 https://ppa.launchpadcontent.net/deadsnakes/ppa/ubuntu jammy/main amd64 python3.12-minimal amd64 3.12.2-1+jammy3 [2,516 kB]\n", + "Get:5 https://ppa.launchpadcontent.net/deadsnakes/ppa/ubuntu jammy/main amd64 libpython3.12-stdlib amd64 3.12.2-1+jammy3 [2,041 kB]\n", + "Get:6 https://ppa.launchpadcontent.net/deadsnakes/ppa/ubuntu jammy/main amd64 python3.12 amd64 3.12.2-1+jammy3 [692 kB]\n", + "Fetched 6,150 kB in 1s (5,187 kB/s)\n", + "Selecting previously unselected package libpython3.12-minimal:amd64.\n", + "(Reading database ... 121753 files and directories currently installed.)\n", + "Preparing to unpack .../0-libpython3.12-minimal_3.12.2-1+jammy3_amd64.deb ...\n", + "Unpacking libpython3.12-minimal:amd64 (3.12.2-1+jammy3) ...\n", + "Selecting previously unselected package python3.12-minimal.\n", + "Preparing to unpack .../1-python3.12-minimal_3.12.2-1+jammy3_amd64.deb ...\n", + "Unpacking python3.12-minimal (3.12.2-1+jammy3) ...\n", + "Selecting previously unselected package mailcap.\n", + "Preparing to unpack .../2-mailcap_3.70+nmu1ubuntu1_all.deb ...\n", + "Unpacking mailcap (3.70+nmu1ubuntu1) ...\n", + "Selecting previously unselected package mime-support.\n", + "Preparing to unpack .../3-mime-support_3.66_all.deb ...\n", + "Unpacking mime-support (3.66) ...\n", + "Selecting previously unselected package libpython3.12-stdlib:amd64.\n", + "Preparing to unpack .../4-libpython3.12-stdlib_3.12.2-1+jammy3_amd64.deb ...\n", + "Unpacking libpython3.12-stdlib:amd64 (3.12.2-1+jammy3) ...\n", + "Selecting previously unselected package python3.12.\n", + "Preparing to unpack .../5-python3.12_3.12.2-1+jammy3_amd64.deb ...\n", + "Unpacking python3.12 (3.12.2-1+jammy3) ...\n", + "Setting up libpython3.12-minimal:amd64 (3.12.2-1+jammy3) ...\n", + "Setting up mailcap (3.70+nmu1ubuntu1) ...\n", + "Setting up python3.12-minimal (3.12.2-1+jammy3) ...\n", + "Setting up mime-support (3.66) ...\n", + "Setting up libpython3.12-stdlib:amd64 (3.12.2-1+jammy3) ...\n", + "Setting up python3.12 (3.12.2-1+jammy3) ...\n", + "Processing triggers for man-db (2.10.2-1) ...\n", + "Collecting git+https://github.com/GitDataAI/jiaozifs_client_py.git\n", + " Cloning https://github.com/GitDataAI/jiaozifs_client_py.git to /tmp/pip-req-build-k4i293l2\n", + " Running command git clone --filter=blob:none --quiet https://github.com/GitDataAI/jiaozifs_client_py.git /tmp/pip-req-build-k4i293l2\n", + " Resolved https://github.com/GitDataAI/jiaozifs_client_py.git to commit f7995a5c0f06250a36aba4e729f0693f71703107\n", + " Preparing metadata (setup.py) ... \u001b[?25l\u001b[?25hdone\n", + "Requirement already satisfied: urllib3>=1.15 in /usr/local/lib/python3.10/dist-packages (from jiaozifs-client==1.0.0) (2.0.7)\n", + "Requirement already satisfied: six>=1.10 in /usr/local/lib/python3.10/dist-packages (from jiaozifs-client==1.0.0) (1.16.0)\n", + "Requirement already satisfied: certifi in /usr/local/lib/python3.10/dist-packages (from jiaozifs-client==1.0.0) (2024.2.2)\n", + "Requirement already satisfied: python-dateutil in /usr/local/lib/python3.10/dist-packages (from jiaozifs-client==1.0.0) (2.8.2)\n", + "Building wheels for collected packages: jiaozifs-client\n", + " Building wheel for jiaozifs-client (setup.py) ... \u001b[?25l\u001b[?25hdone\n", + " Created wheel for jiaozifs-client: filename=jiaozifs_client-1.0.0-py3-none-any.whl size=158905 sha256=0b95168160ce12154cca1cfe618f93451c5e061b9119a0e871e57f0ce65b8898\n", + " Stored in directory: /tmp/pip-ephem-wheel-cache-2o3ps6k3/wheels/2f/56/47/8b5d40bb9b30f398e0ee327501767adb630b69b90631acda18\n", + "Successfully built jiaozifs-client\n", + "Installing collected packages: jiaozifs-client\n", + "Successfully installed jiaozifs-client-1.0.0\n", + "Requirement already satisfied: torchvision in /usr/local/lib/python3.10/dist-packages (0.17.1+cu121)\n", + "Requirement already satisfied: numpy in /usr/local/lib/python3.10/dist-packages (from torchvision) (1.25.2)\n", + "Requirement already satisfied: torch==2.2.1 in /usr/local/lib/python3.10/dist-packages (from torchvision) (2.2.1+cu121)\n", + "Requirement already satisfied: pillow!=8.3.*,>=5.3.0 in /usr/local/lib/python3.10/dist-packages (from torchvision) (9.4.0)\n", + "Requirement already satisfied: filelock in /usr/local/lib/python3.10/dist-packages (from torch==2.2.1->torchvision) (3.13.1)\n", + "Requirement already satisfied: typing-extensions>=4.8.0 in /usr/local/lib/python3.10/dist-packages (from torch==2.2.1->torchvision) (4.10.0)\n", + "Requirement already satisfied: sympy in /usr/local/lib/python3.10/dist-packages (from torch==2.2.1->torchvision) (1.12)\n", + "Requirement already satisfied: networkx in /usr/local/lib/python3.10/dist-packages (from torch==2.2.1->torchvision) (3.2.1)\n", + "Requirement already satisfied: jinja2 in /usr/local/lib/python3.10/dist-packages (from torch==2.2.1->torchvision) (3.1.3)\n", + "Requirement already satisfied: fsspec in /usr/local/lib/python3.10/dist-packages (from torch==2.2.1->torchvision) (2023.6.0)\n", + "Collecting nvidia-cuda-nvrtc-cu12==12.1.105 (from torch==2.2.1->torchvision)\n", + " Downloading nvidia_cuda_nvrtc_cu12-12.1.105-py3-none-manylinux1_x86_64.whl (23.7 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m23.7/23.7 MB\u001b[0m \u001b[31m60.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hCollecting nvidia-cuda-runtime-cu12==12.1.105 (from torch==2.2.1->torchvision)\n", + " Downloading nvidia_cuda_runtime_cu12-12.1.105-py3-none-manylinux1_x86_64.whl (823 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m823.6/823.6 kB\u001b[0m \u001b[31m67.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hCollecting nvidia-cuda-cupti-cu12==12.1.105 (from torch==2.2.1->torchvision)\n", + " Downloading nvidia_cuda_cupti_cu12-12.1.105-py3-none-manylinux1_x86_64.whl (14.1 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m14.1/14.1 MB\u001b[0m \u001b[31m87.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hCollecting nvidia-cudnn-cu12==8.9.2.26 (from torch==2.2.1->torchvision)\n", + " Downloading nvidia_cudnn_cu12-8.9.2.26-py3-none-manylinux1_x86_64.whl (731.7 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m731.7/731.7 MB\u001b[0m \u001b[31m2.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hCollecting nvidia-cublas-cu12==12.1.3.1 (from torch==2.2.1->torchvision)\n", + " Downloading nvidia_cublas_cu12-12.1.3.1-py3-none-manylinux1_x86_64.whl (410.6 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m410.6/410.6 MB\u001b[0m \u001b[31m2.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hCollecting nvidia-cufft-cu12==11.0.2.54 (from torch==2.2.1->torchvision)\n", + " Downloading nvidia_cufft_cu12-11.0.2.54-py3-none-manylinux1_x86_64.whl (121.6 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m121.6/121.6 MB\u001b[0m \u001b[31m8.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hCollecting nvidia-curand-cu12==10.3.2.106 (from torch==2.2.1->torchvision)\n", + " Downloading nvidia_curand_cu12-10.3.2.106-py3-none-manylinux1_x86_64.whl (56.5 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m56.5/56.5 MB\u001b[0m \u001b[31m10.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hCollecting nvidia-cusolver-cu12==11.4.5.107 (from torch==2.2.1->torchvision)\n", + " Downloading nvidia_cusolver_cu12-11.4.5.107-py3-none-manylinux1_x86_64.whl (124.2 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m124.2/124.2 MB\u001b[0m \u001b[31m8.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hCollecting nvidia-cusparse-cu12==12.1.0.106 (from torch==2.2.1->torchvision)\n", + " Downloading nvidia_cusparse_cu12-12.1.0.106-py3-none-manylinux1_x86_64.whl (196.0 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m196.0/196.0 MB\u001b[0m \u001b[31m6.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hCollecting nvidia-nccl-cu12==2.19.3 (from torch==2.2.1->torchvision)\n", + " Downloading nvidia_nccl_cu12-2.19.3-py3-none-manylinux1_x86_64.whl (166.0 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m166.0/166.0 MB\u001b[0m \u001b[31m7.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hCollecting nvidia-nvtx-cu12==12.1.105 (from torch==2.2.1->torchvision)\n", + " Downloading nvidia_nvtx_cu12-12.1.105-py3-none-manylinux1_x86_64.whl (99 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m99.1/99.1 kB\u001b[0m \u001b[31m14.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hRequirement already satisfied: triton==2.2.0 in /usr/local/lib/python3.10/dist-packages (from torch==2.2.1->torchvision) (2.2.0)\n", + "Collecting nvidia-nvjitlink-cu12 (from nvidia-cusolver-cu12==11.4.5.107->torch==2.2.1->torchvision)\n", + " Downloading nvidia_nvjitlink_cu12-12.4.99-py3-none-manylinux2014_x86_64.whl (21.1 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m21.1/21.1 MB\u001b[0m \u001b[31m49.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hRequirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.10/dist-packages (from jinja2->torch==2.2.1->torchvision) (2.1.5)\n", + "Requirement already satisfied: mpmath>=0.19 in /usr/local/lib/python3.10/dist-packages (from sympy->torch==2.2.1->torchvision) (1.3.0)\n", + "Installing collected packages: nvidia-nvtx-cu12, nvidia-nvjitlink-cu12, nvidia-nccl-cu12, nvidia-curand-cu12, nvidia-cufft-cu12, nvidia-cuda-runtime-cu12, nvidia-cuda-nvrtc-cu12, nvidia-cuda-cupti-cu12, nvidia-cublas-cu12, nvidia-cusparse-cu12, nvidia-cudnn-cu12, nvidia-cusolver-cu12\n", + "Successfully installed nvidia-cublas-cu12-12.1.3.1 nvidia-cuda-cupti-cu12-12.1.105 nvidia-cuda-nvrtc-cu12-12.1.105 nvidia-cuda-runtime-cu12-12.1.105 nvidia-cudnn-cu12-8.9.2.26 nvidia-cufft-cu12-11.0.2.54 nvidia-curand-cu12-10.3.2.106 nvidia-cusolver-cu12-11.4.5.107 nvidia-cusparse-cu12-12.1.0.106 nvidia-nccl-cu12-2.19.3 nvidia-nvjitlink-cu12-12.4.99 nvidia-nvtx-cu12-12.1.105\n", + "Requirement already satisfied: Pillow in /usr/local/lib/python3.10/dist-packages (9.4.0)\n", + "Requirement already satisfied: numpy in /usr/local/lib/python3.10/dist-packages (1.25.2)\n", + "Collecting facenet_pytorch\n", + " Downloading facenet_pytorch-2.5.3-py3-none-any.whl (1.9 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.9/1.9 MB\u001b[0m \u001b[31m26.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hRequirement already satisfied: numpy in /usr/local/lib/python3.10/dist-packages (from facenet_pytorch) (1.25.2)\n", + "Requirement already satisfied: requests in /usr/local/lib/python3.10/dist-packages (from facenet_pytorch) (2.31.0)\n", + "Requirement already satisfied: torchvision in /usr/local/lib/python3.10/dist-packages (from facenet_pytorch) (0.17.1+cu121)\n", + "Requirement already satisfied: pillow in /usr/local/lib/python3.10/dist-packages (from facenet_pytorch) (9.4.0)\n", + "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests->facenet_pytorch) (3.3.2)\n", + "Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests->facenet_pytorch) (3.6)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests->facenet_pytorch) (2.0.7)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests->facenet_pytorch) (2024.2.2)\n", + "Requirement already satisfied: torch==2.2.1 in /usr/local/lib/python3.10/dist-packages (from torchvision->facenet_pytorch) (2.2.1+cu121)\n", + "Requirement already satisfied: filelock in /usr/local/lib/python3.10/dist-packages (from torch==2.2.1->torchvision->facenet_pytorch) (3.13.1)\n", + "Requirement already satisfied: typing-extensions>=4.8.0 in /usr/local/lib/python3.10/dist-packages (from torch==2.2.1->torchvision->facenet_pytorch) (4.10.0)\n", + "Requirement already satisfied: sympy in /usr/local/lib/python3.10/dist-packages (from torch==2.2.1->torchvision->facenet_pytorch) (1.12)\n", + "Requirement already satisfied: networkx in /usr/local/lib/python3.10/dist-packages (from torch==2.2.1->torchvision->facenet_pytorch) (3.2.1)\n", + "Requirement already satisfied: jinja2 in /usr/local/lib/python3.10/dist-packages (from torch==2.2.1->torchvision->facenet_pytorch) (3.1.3)\n", + "Requirement already satisfied: fsspec in /usr/local/lib/python3.10/dist-packages (from torch==2.2.1->torchvision->facenet_pytorch) (2023.6.0)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.1.105 in /usr/local/lib/python3.10/dist-packages (from torch==2.2.1->torchvision->facenet_pytorch) (12.1.105)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.1.105 in /usr/local/lib/python3.10/dist-packages (from torch==2.2.1->torchvision->facenet_pytorch) (12.1.105)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.1.105 in /usr/local/lib/python3.10/dist-packages (from torch==2.2.1->torchvision->facenet_pytorch) (12.1.105)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==8.9.2.26 in /usr/local/lib/python3.10/dist-packages (from torch==2.2.1->torchvision->facenet_pytorch) (8.9.2.26)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.1.3.1 in /usr/local/lib/python3.10/dist-packages (from torch==2.2.1->torchvision->facenet_pytorch) (12.1.3.1)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.0.2.54 in /usr/local/lib/python3.10/dist-packages (from torch==2.2.1->torchvision->facenet_pytorch) (11.0.2.54)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.2.106 in /usr/local/lib/python3.10/dist-packages (from torch==2.2.1->torchvision->facenet_pytorch) (10.3.2.106)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.4.5.107 in /usr/local/lib/python3.10/dist-packages (from torch==2.2.1->torchvision->facenet_pytorch) (11.4.5.107)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.1.0.106 in /usr/local/lib/python3.10/dist-packages (from torch==2.2.1->torchvision->facenet_pytorch) (12.1.0.106)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.19.3 in /usr/local/lib/python3.10/dist-packages (from torch==2.2.1->torchvision->facenet_pytorch) (2.19.3)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.1.105 in /usr/local/lib/python3.10/dist-packages (from torch==2.2.1->torchvision->facenet_pytorch) (12.1.105)\n", + "Requirement already satisfied: triton==2.2.0 in /usr/local/lib/python3.10/dist-packages (from torch==2.2.1->torchvision->facenet_pytorch) (2.2.0)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12 in /usr/local/lib/python3.10/dist-packages (from nvidia-cusolver-cu12==11.4.5.107->torch==2.2.1->torchvision->facenet_pytorch) (12.4.99)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.10/dist-packages (from jinja2->torch==2.2.1->torchvision->facenet_pytorch) (2.1.5)\n", + "Requirement already satisfied: mpmath>=0.19 in /usr/local/lib/python3.10/dist-packages (from sympy->torch==2.2.1->torchvision->facenet_pytorch) (1.3.0)\n", + "Installing collected packages: facenet_pytorch\n", + "Successfully installed facenet_pytorch-2.5.3\n", + "Collecting git+https://github.com/GitDataAI/jz_dataloader.git\n", + " Cloning https://github.com/GitDataAI/jz_dataloader.git to /tmp/pip-req-build-c1hx3mat\n", + " Running command git clone --filter=blob:none --quiet https://github.com/GitDataAI/jz_dataloader.git /tmp/pip-req-build-c1hx3mat\n", + " Resolved https://github.com/GitDataAI/jz_dataloader.git to commit be92f4a1604f954a623cba3a383b2cea95f6df73\n", + " Preparing metadata (setup.py) ... \u001b[?25l\u001b[?25hdone\n", + "Building wheels for collected packages: jz-dataloader\n", + " Building wheel for jz-dataloader (setup.py) ... \u001b[?25l\u001b[?25hdone\n", + " Created wheel for jz-dataloader: filename=jz_dataloader-0.0.1-py3-none-any.whl size=9922 sha256=2042752f785714c45301b09762455d812259319157ce9cde7203f252eca726be\n", + " Stored in directory: /tmp/pip-ephem-wheel-cache-n9ixmfi8/wheels/76/85/48/7a6bb9b975ea0d8556f99cb0d2dbf04f3411d4cf40631522a1\n", + "Successfully built jz-dataloader\n", + "Installing collected packages: jz-dataloader\n", + "Successfully installed jz-dataloader-0.0.1\n" + ] + } + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "id": "4kIYz5w70WUs" + }, + "outputs": [], + "source": [ + "from facenet_pytorch import MTCNN, InceptionResnetV1\n", + "import torch\n", + "from torch.utils.data import DataLoader\n", + "from torchvision import datasets\n", + "import numpy as np\n", + "import pandas as pd\n", + "import os\n", + "\n", + "workers = 0 if os.name == 'nt' else 4" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "YpOroE8D0WUs" + }, + "source": [ + "#### Determine if an nvidia GPU is available" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "id": "TJwR6sMz0WUs", + "outputId": "b90dfc71-b7c5-4c8e-8a34-d4c828b85314", + "colab": { + "base_uri": "https://localhost:8080/" + } + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Running on device: cpu\n" + ] + } + ], + "source": [ + "device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\n", + "print('Running on device: {}'.format(device))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "fiVYj6eO0WUt" + }, + "source": [ + "#### Define MTCNN module\n", + "\n", + "Default params shown for illustration, but not needed. Note that, since MTCNN is a collection of neural nets and other code, the device must be passed in the following way to enable copying of objects when needed internally.\n", + "\n", + "See `help(MTCNN)` for more details." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "id": "xtHheWw-0WUt" + }, + "outputs": [], + "source": [ + "mtcnn = MTCNN(\n", + " image_size=160, margin=0, min_face_size=20,\n", + " thresholds=[0.6, 0.7, 0.7], factor=0.709, post_process=True,\n", + " device=device\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "JdY9Zjo_0WUt" + }, + "source": [ + "#### Define Inception Resnet V1 module\n", + "\n", + "Set classify=True for pretrained classifier. For this example, we will use the model to output embeddings/CNN features. Note that for inference, it is important to set the model to `eval` mode.\n", + "\n", + "See `help(InceptionResnetV1)` for more details." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "id": "sFMl6CGw0WUt", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 49, + "referenced_widgets": [ + "5ea20bab0a664855a1ff8c0053d47b0b", + "6c2c64b4f2c04660a2dd08953d1a5bca", + "b2cd0aba2e82445ca726b8548b5e029c", + "d17bc5fe5c884ed985a51a16667647c4", + "7411b0b54afa45f0a2362a9715957208", + "4f7c1ef96aee4382a089c9efb58e0e74", + "0221c74b04694a1e8e21ed5784a9177b", + "fbd0f22afc5b4a899a0daf4d4ccc6011", + "5156e9b925cf41c7a8de72149c5ba109", + "e9c6c3244b164bd9bb0aed9feb7c442b", + "51186066124e464a9c6c675a24422193" + ] + }, + "outputId": "dd88df64-cce7-4c7a-e627-a6ad18b0240b" + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + " 0%| | 0.00/107M [00:00 Date: Thu, 28 Mar 2024 00:07:02 +0800 Subject: [PATCH 207/210] update content (#166) * update content * add usecases * minor update --- README.md | 53 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 4a9946aa..88c61043 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# JiaoziFS (JZFS) -A version control file system for data centric applications & teams. +# JiaoZiFS (JZFS) +A version control file system for data linage & data collaboration.

@@ -12,18 +12,18 @@ A version control file system for data centric applications & teams. ---- -### What is JiaoziFS? -JiaoziFS is an industry-leading **Data-Centric Version Control** File System, helps ensure Responsible AI Engineering by improving **Data Versioning**, **Provenance**, and **Reproducibility**. +### What is JiaoZiFS? +JiaoZiFS is an industry-leading **Data-Centric Version Control** File System, helps ensure Responsible AI Engineering by improving **Data Versioning**, **Provenance**, and **Reproducibility**. Note: -* The name Jiaozi pays tribute to the world's earliest paper money: [Song Dynasty Jiaozi](https://en.wikipedia.org/wiki/Jiaozi_(currency)). -* JiaoziFS is yet another implementation of [IPFS (InterPlanetary File System)](https://ipfs.tech/) as JiaoziFS will be compatible with the [implementation requirements](https://specs.ipfs.tech/architecture/principles/#ipfs-implementation-requirements) of IPFS. -* As a filesystem of data versioning at scale, although JiaoziFS is built for machine learning, It has a wide range of use scenarios (refer A Universe of Uses) and can be seamlessly integrated into all your data stack. +* The name JiaoZi pays tribute to the world's earliest paper money: [Song Dynasty JiaoZi](https://en.wikipedia.org/wiki/Jiaozi_(currency)). +* JiaoZiFS is yet another implementation of [IPFS (InterPlanetary File System)](https://ipfs.tech/) as JiaoZiFS will be compatible with the [implementation requirements](https://specs.ipfs.tech/architecture/principles/#ipfs-implementation-requirements) of IPFS. +* As a filesystem of data versioning at scale, although JiaoZiFS is built for machine learning, It has a wide range of use scenarios (refer A Universe of Uses) and can be seamlessly integrated into all your data stack. Data-centric AI is about the practice of iterating and collaborating on data, used to build AI systems, programmatically. Machine learning pioneer Andrew Ng [argues that focusing on the quality of data fueling AI systems will help unlock its full power](https://youtu.be/TU6u_T-s68Y). ---- -### Why JiaoziFS? +### Why JiaoZiFS? In production systems with machine learning components, updates and experiments are frequent. New updates to models(data products) may be released every day or every few minutes, and different users may see the results of different models as part of A/B experiments or canary releases. * **Version Everything**: Data scientists are often criticized for being less disciplined with versioning their experiments(versioning of data, pipeline, code, and models), especially when using computational notebooks. @@ -32,24 +32,35 @@ In production systems with machine learning components, updates and experiments ---- ### A Universe of Uses -JiaoziFS's versatility shines across different industries – making it the multi-purpose tool for the **data centric applications and teams**. - -* **Enterprise DataHub & Data Collaboration**: Depending on your operating scale, you may even be managing multiple team members, who may be spread across different locations. JiaoziFS enable Collaborative Datasets Version Management at Scale,Share & collaborate easily: Instantly share insights and co-edit with your team. -* **DataOps & Data Products & Data Mesh**: Augmenting Enterprise Data Development and Operations,JiaoziFS ensures Responsible DataOps/AIOps/MLOps by improving Data Versioning, Provenance, and Reproducibility. JiaoziFS makes a fusion of data science and product development and allows data to be containerized into shareable, tradeable, and trackable assets(data products or data NFTs). Versioning data products in a maturing Data Mesh environment via standard processes, data consumers can be informed about both breaking and non-breaking changes in a data product, as well as retirement of data products. +JiaoZiFS's versatility shines across different industries – making it the multi-purpose tool for the **data centric applications and teams**. + + +* **Defining artificial intelligence in the context of lineage**: Artificial intelligence (AI) is an umbrella term that covers a variety of techniques and approaches that make it possible for machines to learn, adjust and act with intelligence comparable to the natural intelligence of humans. Lineage has direct implications for many of the techniques and approaches of AI, such as: + * Neural networks. AI classifies data to make predictions and decisions in much the same way a human brain does. A neural network is a computing system made up of interconnected units (like neurons) that process data from external inputs, relaying information between each unit. The neural network requires multiple passes at the data to find connections and derive meaning from undefined data. Neural networks benefit greatly from the movement aspects of data lineage – because connecting those dots directs its search for meaning. + * Natural language processing. AI that enables interaction, understanding and communication between humans and machines by analyzing and generating human language, including speech, is called natural language processing (NLP). NLP allows humans to communicate with computers using normal, everyday language to perform tasks. Natural language processing relies heavily on the human language data descriptions provided by the characteristics aspect of data lineage. + * Machine learning. AI that’s focused on giving machines access to data and letting them learn for themselves is known as machine learning. Machine learning automates analytical model building using methods from neural networks, statistics, operations research and physics – and it finds hidden insights in data without being explicitly programmed where to look or what to conclude. Machine learning delves into the relationships, processes and transformations aspects of data lineage during its undirected exploration of data’s potential. + * Deep learning. With deep learning, AI uses huge neural networks with many layers of processing to learn complex patterns in large amounts of data and perform humanlike tasks, such as recognizing speech or understanding images and videos (also known as computer vision). This method takes advantage of advances in computing power and improved training techniques. Deep learning depends on the users’ aspect of data lineage because its education is guided by analyzing how users interact with data. +* **Solving the Mysteries of Data Science’s Past and Present with Data Lineage**: Data lineage is an essential aspect of data science and data analytics that enables organizations to understand the journey of data from its origin to its destination. It is a process of tracking the origin, movement, and transformation of data through various stages of its lifecycle. Data lineage plays a critical role in enhancing the performance and productivity of data science and analytics teams. + * By establishing a clear data lineage, data scientists and analysts can easily identify the source of data, its quality, and its dependencies. This information is crucial in ensuring data accuracy, reliability, and consistency. Additionally, data lineage helps teams quickly identify errors or issues in the data processing pipeline, enabling them to take corrective action promptly. + * Moreover, data lineage promotes better collaboration among team members by providing a shared understanding of the data ecosystem. This shared understanding ensures that everyone involved in the data analysis process is on the same page, reducing confusion and errors caused by miscommunication. +* **Enterprise DataHub & Data Collaboration**: Depending on your operating scale, you may even be managing multiple team members, who may be spread across different locations. JiaoZiFS enable Collaborative Datasets Version Management at Scale,Share & collaborate easily: Instantly share insights and co-edit with your team. +* **DataOps & Data Products & Data Mesh**: Augmenting Enterprise Data Development and Operations,JiaoZiFS ensures Responsible DataOps/AIOps/MLOps by improving Data Versioning, Provenance, and Reproducibility. JiaoziFS makes a fusion of data science and product development and allows data to be containerized into shareable, tradeable, and trackable assets(data products or data NFTs). Versioning data products in a maturing Data Mesh environment via standard processes, data consumers can be informed about both breaking and non-breaking changes in a data product, as well as retirement of data products. * **Industrial Digital Twin**: Developing digital twins for manufacturing involves managing tons of large files and multiple iterations of a project. All of the data collected and created in the digital twin process (and there is a lot of it) needs to be managed carefully. JiaoziFS allows you to manage changes to files over time and store these modifications in a database. -* **Data Lake Management**: Data lakes are dynamic. New files and new versions of ex- isting files enter the lake at the ingestion stage. Additionally, extractors can evolve over time and generate new versions of raw data. As a result, data lake versioning is a cross-cutting concern across all stages of a data lake. Of course vanilla dis- tributed file systems are not adequate for versioning-related operations. For example, simply storing all versions may be too costly for large datasets, and without a good version manager, just using filenames to track versions can be error-prone. In a data lake, for which there are usually many users, it is even more important to clearly maintain correct versions being used and evolving across different users. Furthermore, as the number of versions increases, efficiently and cost-effectively providing storage and retrieval of versions is going to be an important feature of a successful data lake system. +* **Data Lake Management**: Data lakes are dynamic. New files and new versions of ex- isting files enter the lake at the ingestion stage. Additionally, extractors can evolve over time and generate new versions of raw data. As a result, data lake versioning is a cross-cutting concern across all stages of a data lake. Of course vanilla distributed file systems are not adequate for versioning-related operations. For example, simply storing all versions may be too costly for large datasets, and without a good version manager, just using filenames to track versions can be error-prone. In a data lake, for which there are usually many users, it is even more important to clearly maintain correct versions being used and evolving across different users. Furthermore, as the number of versions increases, efficiently and cost-effectively providing storage and retrieval of versions is going to be an important feature of a successful data lake system. + + ---- ### Specification -[JiaoziFS Specification](https://github.com/GitDataAI/Specification/blob/main/JiaoziFS) +[JiaoZiFS Specification](https://github.com/GitDataAI/Specification/blob/main/JiaoziFS) ---- ### Basic Build And Usage #### Requirement -1. To build JiaoziFS, you need a working installation of [Go 1.22.0 or higher](https://golang.org/dl/) -2. JiaoziFS use postgres to store running data, you can install at [postgres install installation guide](https://www.postgresql.org/docs/current/installation.html) +1. To build JiaoZiFS, you need a working installation of [Go 1.22.0 or higher](https://golang.org/dl/) +2. JiaoZiFS use postgres to store running data, you can install at [postgres install installation guide](https://www.postgresql.org/docs/current/installation.html) #### Build And Running @@ -79,6 +90,16 @@ docker run -v :/app -p 34913:34913 gitdatateam/jzfs:latest --db "postgres [Try without installing](https://cloud.jiaozifs.com) +Note: storage config for IPFS backend storage as you create a new repository in JiaoZiFS UI. + +``` + {"type":"ipfs","ipfs":{"url":"/dns/kubo-service.ipfs.svc.cluster.local/tcp/5001"}} +``` + +### Build AL/ML pipeline over JiaoZiFS + +[Face detection and recognition inference pipeline](https://colab.research.google.com/drive/1wsv-KMxTdsCLZ64eLq4W1MTfspid-vv6?usp=sharing) + ---- ### Contributors From 6957b746a6b7708174f0b19feca117339ceae073 Mon Sep 17 00:00:00 2001 From: Mike <41407352+hunjixin@users.noreply.github.com> Date: Tue, 28 May 2024 13:45:24 +0800 Subject: [PATCH 208/210] fix: response missing repository field (#168) --- controller/repository_ctl.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/controller/repository_ctl.go b/controller/repository_ctl.go index fa8d34db..c7b996de 100644 --- a/controller/repository_ctl.go +++ b/controller/repository_ctl.go @@ -614,12 +614,17 @@ func (repositoryCtl RepositoryController) GetArchive(ctx context.Context, w *api func repositoryToDto(repository *models.Repository) *api.Repository { return &api.Repository{ - CreatedAt: repository.CreatedAt.UnixMilli(), - CreatorId: repository.CreatorID, - Description: repository.Description, - Head: repository.HEAD, - Id: repository.ID, - Name: repository.Name, - UpdatedAt: repository.UpdatedAt.UnixMilli(), + CreatedAt: repository.CreatedAt.UnixMilli(), + CreatorId: repository.CreatorID, + Description: repository.Description, + Visible: repository.Visible, + Head: repository.HEAD, + Id: repository.ID, + Name: repository.Name, + UpdatedAt: repository.UpdatedAt.UnixMilli(), + OwnerId: repository.OwnerID, + StorageAdapterParams: repository.StorageAdapterParams, + StorageNamespace: repository.StorageNamespace, + UsePublicStorage: repository.UsePublicStorage, } } From 300c4be47f3782a501e294dcfc7cb4d3a526e212 Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Sun, 9 Jun 2024 19:39:05 +0800 Subject: [PATCH 209/210] trigger a deploy --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 88c61043..f183e128 100644 --- a/README.md +++ b/README.md @@ -115,3 +115,4 @@ Note: storage config for IPFS backend storage as you create a new repository in Dual-licensed under [MIT](https://github.com/GitDataAI/jiaozifs/blob/main/LICENSE-MIT) + [Apache 2.0](https://github.com/GitDataAI/jiaozifs/blob/main/LICENSE-APACHE) +just trick to start a deploy \ No newline at end of file From a6002fc5f858849e9acbf5ddb9c3cb684e5958fd Mon Sep 17 00:00:00 2001 From: hunjixin <1084400399@qq.com> Date: Sun, 9 Jun 2024 20:19:18 +0800 Subject: [PATCH 210/210] fix eof --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f183e128..dcdd453a 100644 --- a/README.md +++ b/README.md @@ -115,4 +115,4 @@ Note: storage config for IPFS backend storage as you create a new repository in Dual-licensed under [MIT](https://github.com/GitDataAI/jiaozifs/blob/main/LICENSE-MIT) + [Apache 2.0](https://github.com/GitDataAI/jiaozifs/blob/main/LICENSE-APACHE) -just trick to start a deploy \ No newline at end of file +just trick to start a deploy