Extract leaf package
This commit is contained in:
		
						commit
						2dd1fb91c7
					
				
							
								
								
									
										52
									
								
								.golangci.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								.golangci.yml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					run:
 | 
				
			||||||
 | 
					  timeout: 5m
 | 
				
			||||||
 | 
					  output:
 | 
				
			||||||
 | 
					    format: tab
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					linters-settings:
 | 
				
			||||||
 | 
					  govet:
 | 
				
			||||||
 | 
					    check-shadowing: true
 | 
				
			||||||
 | 
					  golint:
 | 
				
			||||||
 | 
					    min-confidence: 0.1
 | 
				
			||||||
 | 
					  maligned:
 | 
				
			||||||
 | 
					    suggest-new: true
 | 
				
			||||||
 | 
					  goconst:
 | 
				
			||||||
 | 
					    min-len: 2
 | 
				
			||||||
 | 
					    min-occurrences: 2
 | 
				
			||||||
 | 
					  misspell:
 | 
				
			||||||
 | 
					    locale: US
 | 
				
			||||||
 | 
					  lll:
 | 
				
			||||||
 | 
					    line-length: 140
 | 
				
			||||||
 | 
					  gocritic:
 | 
				
			||||||
 | 
					    enabled-tags:
 | 
				
			||||||
 | 
					      - performance
 | 
				
			||||||
 | 
					      - style
 | 
				
			||||||
 | 
					      - experimental
 | 
				
			||||||
 | 
					    disabled-checks:
 | 
				
			||||||
 | 
					      - unnamedResult
 | 
				
			||||||
 | 
					      - paramTypeCombine
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					linters:
 | 
				
			||||||
 | 
					  enable:
 | 
				
			||||||
 | 
					    - megacheck
 | 
				
			||||||
 | 
					    - revive
 | 
				
			||||||
 | 
					    - govet
 | 
				
			||||||
 | 
					    - unconvert
 | 
				
			||||||
 | 
					    - megacheck
 | 
				
			||||||
 | 
					    - unused
 | 
				
			||||||
 | 
					    - gas
 | 
				
			||||||
 | 
					    - gocyclo
 | 
				
			||||||
 | 
					    - dupl
 | 
				
			||||||
 | 
					    - misspell
 | 
				
			||||||
 | 
					    - unparam
 | 
				
			||||||
 | 
					    - typecheck
 | 
				
			||||||
 | 
					    - ineffassign
 | 
				
			||||||
 | 
					    - stylecheck
 | 
				
			||||||
 | 
					    - gochecknoinits
 | 
				
			||||||
 | 
					    - exportloopref
 | 
				
			||||||
 | 
					    - gocritic
 | 
				
			||||||
 | 
					    - nakedret
 | 
				
			||||||
 | 
					    - gosimple
 | 
				
			||||||
 | 
					    - prealloc
 | 
				
			||||||
 | 
					  fast: false
 | 
				
			||||||
 | 
					  disable-all: true
 | 
				
			||||||
							
								
								
									
										37
									
								
								document.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								document.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					package leaf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"go.mongodb.org/mongo-driver/bson/primitive"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Document interface {
 | 
				
			||||||
 | 
						ID() primitive.ObjectID
 | 
				
			||||||
 | 
						SetID(id primitive.ObjectID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						SetCreatedAt(ts time.Time)
 | 
				
			||||||
 | 
						SetUpdatedAt(ts time.Time)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Base struct {
 | 
				
			||||||
 | 
						ObjectID  primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty" example:"507f1f77bcf86cd799439011"`
 | 
				
			||||||
 | 
						CreatedAt time.Time          `json:"created_at" bson:"created_at" format:"dateTime" example:"2020-01-01T00:00:00Z"`
 | 
				
			||||||
 | 
						UpdatedAt time.Time          `json:"updated_at" bson:"updated_at" format:"dateTime" example:"2020-01-01T00:00:00Z"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *Base) ID() primitive.ObjectID {
 | 
				
			||||||
 | 
						return s.ObjectID
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *Base) SetID(id primitive.ObjectID) {
 | 
				
			||||||
 | 
						s.ObjectID = id
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *Base) SetCreatedAt(ts time.Time) {
 | 
				
			||||||
 | 
						s.CreatedAt = ts
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *Base) SetUpdatedAt(ts time.Time) {
 | 
				
			||||||
 | 
						s.UpdatedAt = ts
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										51
									
								
								document_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								document_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					package leaf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
						"go.mongodb.org/mongo-driver/bson"
 | 
				
			||||||
 | 
						"go.mongodb.org/mongo-driver/bson/primitive"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestBaseInitialization(t *testing.T) {
 | 
				
			||||||
 | 
						id := primitive.NewObjectID()
 | 
				
			||||||
 | 
						now := time.Now()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						base := Base{}
 | 
				
			||||||
 | 
						base.SetID(id)
 | 
				
			||||||
 | 
						base.SetCreatedAt(now)
 | 
				
			||||||
 | 
						base.SetUpdatedAt(now)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.Equal(t, id, base.ID())
 | 
				
			||||||
 | 
						assert.Equal(t, id, base.ObjectID)
 | 
				
			||||||
 | 
						assert.True(t, base.CreatedAt.Equal(now))
 | 
				
			||||||
 | 
						assert.True(t, base.UpdatedAt.Equal(now))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestBaseJSONMarshalling(t *testing.T) {
 | 
				
			||||||
 | 
						base := Base{}
 | 
				
			||||||
 | 
						base.SetID(primitive.NewObjectID())
 | 
				
			||||||
 | 
						base.SetCreatedAt(time.Now())
 | 
				
			||||||
 | 
						base.SetUpdatedAt(time.Now())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data, err := json.Marshal(base)
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
						assert.True(t, json.Valid(data))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestBaseBSONMarshalling(t *testing.T) {
 | 
				
			||||||
 | 
						base := Base{}
 | 
				
			||||||
 | 
						base.SetID(primitive.NewObjectID())
 | 
				
			||||||
 | 
						base.SetCreatedAt(time.Now())
 | 
				
			||||||
 | 
						base.SetUpdatedAt(time.Now())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data, err := bson.Marshal(base)
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var m bson.M
 | 
				
			||||||
 | 
						err = bson.Unmarshal(data, &m)
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										84
									
								
								filter.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								filter.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,84 @@
 | 
				
			|||||||
 | 
					package leaf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						FilterAll = FilterDocument{}
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						OperatorEq     Operator = "$eq"
 | 
				
			||||||
 | 
						OperatorNe     Operator = "$ne"
 | 
				
			||||||
 | 
						OperatorGt     Operator = "$gt"
 | 
				
			||||||
 | 
						OperatorGte    Operator = "$gte"
 | 
				
			||||||
 | 
						OperatorLt     Operator = "$lt"
 | 
				
			||||||
 | 
						OperatorLte    Operator = "$lte"
 | 
				
			||||||
 | 
						OperatorIn     Operator = "$in"
 | 
				
			||||||
 | 
						OperatorNin    Operator = "$nin"
 | 
				
			||||||
 | 
						OperatorExists Operator = "$exists"
 | 
				
			||||||
 | 
						OperatorRegex  Operator = "$regex"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type M map[Operator]any
 | 
				
			||||||
 | 
					type Operator string
 | 
				
			||||||
 | 
					type FilterDocument map[string]M
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Eq[T any](field string, value T) FilterDocument {
 | 
				
			||||||
 | 
						return createSelector(field, OperatorEq, value)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Ne[T any](field string, value T) FilterDocument {
 | 
				
			||||||
 | 
						return createSelector(field, OperatorNe, value)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Gt[T any](field string, value T) FilterDocument {
 | 
				
			||||||
 | 
						return createSelector(field, OperatorGt, value)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Gte[T any](field string, value T) FilterDocument {
 | 
				
			||||||
 | 
						return createSelector(field, OperatorGte, value)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Lt[T any](field string, value T) FilterDocument {
 | 
				
			||||||
 | 
						return createSelector(field, OperatorLt, value)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Lte[T any](field string, value T) FilterDocument {
 | 
				
			||||||
 | 
						return createSelector(field, OperatorLte, value)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func In[T any](field string, value []T) FilterDocument {
 | 
				
			||||||
 | 
						return createSelector(field, OperatorIn, value)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Nin[T any](field string, value []T) FilterDocument {
 | 
				
			||||||
 | 
						return createSelector(field, OperatorNin, value)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Exists(field string, value bool) FilterDocument {
 | 
				
			||||||
 | 
						return createSelector(field, OperatorExists, value)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Regex(field string, value string) FilterDocument {
 | 
				
			||||||
 | 
						return createSelector(field, OperatorRegex, value)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func createSelector(field string, operator Operator, value any) FilterDocument {
 | 
				
			||||||
 | 
						return FilterDocument{field: M{operator: value}}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Filter(docs ...FilterDocument) FilterDocument {
 | 
				
			||||||
 | 
						query := FilterDocument{}
 | 
				
			||||||
 | 
						for _, doc := range docs {
 | 
				
			||||||
 | 
							for field, operators := range doc {
 | 
				
			||||||
 | 
								if _, ok := query[field]; !ok {
 | 
				
			||||||
 | 
									query[field] = operators
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for op, value := range operators {
 | 
				
			||||||
 | 
									query[field][op] = value
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return query
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										56
									
								
								filter_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								filter_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					package leaf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestFilter(t *testing.T) {
 | 
				
			||||||
 | 
						t.Run("merge two filter documents", func(t *testing.T) {
 | 
				
			||||||
 | 
							doc1 := Filter(
 | 
				
			||||||
 | 
								Gt("field1", 1),
 | 
				
			||||||
 | 
								Lt("field2", 10),
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							doc2 := Filter(
 | 
				
			||||||
 | 
								Ne("field1", 2),
 | 
				
			||||||
 | 
								Eq("field3", "test"),
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							expected := FilterDocument{
 | 
				
			||||||
 | 
								"field1": {"$gt": 1, "$ne": 2},
 | 
				
			||||||
 | 
								"field2": {"$lt": 10},
 | 
				
			||||||
 | 
								"field3": {"$eq": "test"},
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert.Equal(t, expected, Filter(doc1, doc2))
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("merge three filter documents", func(t *testing.T) {
 | 
				
			||||||
 | 
							doc1 := Filter(
 | 
				
			||||||
 | 
								Gt("field1", 1),
 | 
				
			||||||
 | 
								Lt("field2", 10),
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							doc2 := Filter(
 | 
				
			||||||
 | 
								Ne("field1", 2),
 | 
				
			||||||
 | 
								Eq("field3", "test"),
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							doc3 := Filter(
 | 
				
			||||||
 | 
								Gt("field1", 5),
 | 
				
			||||||
 | 
								Ne("field2", 5),
 | 
				
			||||||
 | 
								Eq("field4", "hello"),
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							expected := FilterDocument{
 | 
				
			||||||
 | 
								"field1": {"$gt": 5, "$ne": 2},
 | 
				
			||||||
 | 
								"field2": {"$lt": 10, "$ne": 5},
 | 
				
			||||||
 | 
								"field3": {"$eq": "test"},
 | 
				
			||||||
 | 
								"field4": {"$eq": "hello"},
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert.Equal(t, expected, Filter(doc1, doc2, doc3))
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										28
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					module go.pkg.cx/leaf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					go 1.21
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						github.com/rs/zerolog v1.31.0
 | 
				
			||||||
 | 
						github.com/stretchr/testify v1.8.4
 | 
				
			||||||
 | 
						go.mongodb.org/mongo-driver v1.13.1
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						github.com/davecgh/go-spew v1.1.1 // indirect
 | 
				
			||||||
 | 
						github.com/golang/snappy v0.0.1 // indirect
 | 
				
			||||||
 | 
						github.com/klauspost/compress v1.13.6 // indirect
 | 
				
			||||||
 | 
						github.com/mattn/go-colorable v0.1.13 // indirect
 | 
				
			||||||
 | 
						github.com/mattn/go-isatty v0.0.19 // indirect
 | 
				
			||||||
 | 
						github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
 | 
				
			||||||
 | 
						github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/xdg-go/pbkdf2 v1.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/xdg-go/scram v1.1.2 // indirect
 | 
				
			||||||
 | 
						github.com/xdg-go/stringprep v1.0.4 // indirect
 | 
				
			||||||
 | 
						github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
 | 
				
			||||||
 | 
						golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
 | 
				
			||||||
 | 
						golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
 | 
				
			||||||
 | 
						golang.org/x/sys v0.12.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/text v0.7.0 // indirect
 | 
				
			||||||
 | 
						gopkg.in/yaml.v3 v3.0.1 // indirect
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										77
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,77 @@
 | 
				
			|||||||
 | 
					github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 | 
				
			||||||
 | 
					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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 | 
				
			||||||
 | 
					github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
 | 
				
			||||||
 | 
					github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 | 
				
			||||||
 | 
					github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
 | 
				
			||||||
 | 
					github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 | 
				
			||||||
 | 
					github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
 | 
				
			||||||
 | 
					github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
 | 
				
			||||||
 | 
					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.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/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
 | 
				
			||||||
 | 
					github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 | 
				
			||||||
 | 
					github.com/pkg/errors v0.9.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/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
 | 
				
			||||||
 | 
					github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
 | 
				
			||||||
 | 
					github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
 | 
				
			||||||
 | 
					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/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
 | 
				
			||||||
 | 
					github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
 | 
				
			||||||
 | 
					github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
 | 
				
			||||||
 | 
					github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
 | 
				
			||||||
 | 
					github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
 | 
				
			||||||
 | 
					github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
 | 
				
			||||||
 | 
					github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
 | 
				
			||||||
 | 
					github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
 | 
				
			||||||
 | 
					github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 | 
				
			||||||
 | 
					go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk=
 | 
				
			||||||
 | 
					go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 | 
				
			||||||
 | 
					golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-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.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=
 | 
				
			||||||
 | 
					golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
				
			||||||
 | 
					golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
				
			||||||
 | 
					golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
				
			||||||
 | 
					golang.org/x/text v0.3.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 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
 | 
				
			||||||
 | 
					golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 | 
				
			||||||
 | 
					golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
				
			||||||
 | 
					golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 | 
				
			||||||
 | 
					golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
				
			||||||
 | 
					gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 | 
				
			||||||
 | 
					gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
				
			||||||
							
								
								
									
										98
									
								
								index.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								index.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,98 @@
 | 
				
			|||||||
 | 
					package leaf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/rs/zerolog"
 | 
				
			||||||
 | 
						"go.mongodb.org/mongo-driver/bson"
 | 
				
			||||||
 | 
						"go.mongodb.org/mongo-driver/bson/primitive"
 | 
				
			||||||
 | 
						driver "go.mongodb.org/mongo-driver/mongo"
 | 
				
			||||||
 | 
						"go.mongodb.org/mongo-driver/mongo/options"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ensureIndexes(ctx context.Context, collection *driver.Collection, models []driver.IndexModel, createIndexes bool, logger *zerolog.Logger) error {
 | 
				
			||||||
 | 
						existing, err := indexNames(ctx, collection)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						indexes := filterIndexes(models, existing, logger)
 | 
				
			||||||
 | 
						if len(indexes) < 1 {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !createIndexes {
 | 
				
			||||||
 | 
							for _, im := range indexes {
 | 
				
			||||||
 | 
								logger.Warn().Str("name", *im.Options.Name).Msg("index declared but not present")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = collection.Indexes().CreateMany(ctx, indexes)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, im := range indexes {
 | 
				
			||||||
 | 
							logger.Info().Str("name", *im.Options.Name).Msg("index created")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func indexNames(ctx context.Context, collection *driver.Collection) (map[string]struct{}, error) {
 | 
				
			||||||
 | 
						cursor, err := collection.Indexes().List(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer cursor.Close(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						names := make(map[string]struct{})
 | 
				
			||||||
 | 
						for cursor.Next(ctx) {
 | 
				
			||||||
 | 
							var idx bson.M
 | 
				
			||||||
 | 
							if err := cursor.Decode(&idx); err != nil {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							name, ok := idx["name"].(string)
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							names[name] = struct{}{}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return names, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func filterIndexes(models []driver.IndexModel, names map[string]struct{}, logger *zerolog.Logger) []driver.IndexModel {
 | 
				
			||||||
 | 
						var filtered []driver.IndexModel
 | 
				
			||||||
 | 
						for _, im := range models {
 | 
				
			||||||
 | 
							if im.Options.Name == nil {
 | 
				
			||||||
 | 
								logger.Warn().Interface("keys", im.Keys).Msg("must specify index name explicitly")
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if _, ok := names[*im.Options.Name]; !ok {
 | 
				
			||||||
 | 
								filtered = append(filtered, im)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return filtered
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func withTimestampIndexes(models []driver.IndexModel) []driver.IndexModel {
 | 
				
			||||||
 | 
						tsModels := []driver.IndexModel{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Keys:    bson.D{primitive.E{Key: "created_at", Value: 1}},
 | 
				
			||||||
 | 
								Options: options.Index().SetName("created_at_1"),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Keys:    bson.D{primitive.E{Key: "updated_at", Value: 1}},
 | 
				
			||||||
 | 
								Options: options.Index().SetName("updated_at_1"),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return append(models, tsModels...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								objectid.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								objectid.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					package leaf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"go.mongodb.org/mongo-driver/bson/primitive"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ObjectIDFromHex(hex string) primitive.ObjectID {
 | 
				
			||||||
 | 
						id, err := primitive.ObjectIDFromHex(hex)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return primitive.NilObjectID
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return id
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ObjectIDFromHexSlice(hexSlice []string) []primitive.ObjectID {
 | 
				
			||||||
 | 
						ids := make([]primitive.ObjectID, len(hexSlice))
 | 
				
			||||||
 | 
						for i := range hexSlice {
 | 
				
			||||||
 | 
							ids[i] = ObjectIDFromHex(hexSlice[i])
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ids
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										40
									
								
								objectid_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								objectid_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					package leaf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
						"go.mongodb.org/mongo-driver/bson/primitive"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestObjectIDFromHex(t *testing.T) {
 | 
				
			||||||
 | 
						t.Run("valid hex", func(t *testing.T) {
 | 
				
			||||||
 | 
							hex := "507f1f77bcf86cd799439011"
 | 
				
			||||||
 | 
							id, err := primitive.ObjectIDFromHex(hex)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							assert.Equal(t, id, ObjectIDFromHex(hex))
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("invalid hex", func(t *testing.T) {
 | 
				
			||||||
 | 
							hex := "invalidhex"
 | 
				
			||||||
 | 
							assert.Equal(t, primitive.NilObjectID, ObjectIDFromHex(hex))
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestObjectIDFromHexSlice(t *testing.T) {
 | 
				
			||||||
 | 
						t.Run("valid hex slice", func(t *testing.T) {
 | 
				
			||||||
 | 
							hex := []string{"507f1f77bcf86cd799439011", "507f1f77bcf86cd799439012"}
 | 
				
			||||||
 | 
							ids := make([]primitive.ObjectID, len(hex))
 | 
				
			||||||
 | 
							for i := range hex {
 | 
				
			||||||
 | 
								ids[i], _ = primitive.ObjectIDFromHex(hex[i])
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert.Equal(t, ids, ObjectIDFromHexSlice(hex))
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("invalid hex slice", func(t *testing.T) {
 | 
				
			||||||
 | 
							hex := []string{"invalidhex", "invalidhex"}
 | 
				
			||||||
 | 
							assert.Equal(t, []primitive.ObjectID{primitive.NilObjectID, primitive.NilObjectID}, ObjectIDFromHexSlice(hex))
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										118
									
								
								repository.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								repository.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,118 @@
 | 
				
			|||||||
 | 
					package leaf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/rs/zerolog"
 | 
				
			||||||
 | 
						"go.mongodb.org/mongo-driver/bson"
 | 
				
			||||||
 | 
						"go.mongodb.org/mongo-driver/bson/primitive"
 | 
				
			||||||
 | 
						driver "go.mongodb.org/mongo-driver/mongo"
 | 
				
			||||||
 | 
						"go.mongodb.org/mongo-driver/mongo/options"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type RepositoryInterface[T Document] interface {
 | 
				
			||||||
 | 
						Find(ctx context.Context, filter interface{}, opts ...*options.FindOptions) ([]T, error)
 | 
				
			||||||
 | 
						FindOne(ctx context.Context, filter interface{}, opts ...*options.FindOneOptions) (T, error)
 | 
				
			||||||
 | 
						FindByID(ctx context.Context, id string, opts ...*options.FindOneOptions) (T, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Create(ctx context.Context, data T, opts ...*options.InsertOneOptions) (T, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Update(ctx context.Context, data T, opts ...*options.UpdateOptions) (T, error)
 | 
				
			||||||
 | 
						UpdateOne(ctx context.Context, filter interface{}, data T, opts ...*options.UpdateOptions) (T, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Delete(ctx context.Context, data T, opts ...*options.DeleteOptions) error
 | 
				
			||||||
 | 
						DeleteOne(ctx context.Context, filter interface{}, opts ...*options.DeleteOptions) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CountDocuments(ctx context.Context, filter interface{}, opts ...*options.CountOptions) (int, error)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Repository[T Document] struct {
 | 
				
			||||||
 | 
						collection *driver.Collection
 | 
				
			||||||
 | 
						indexes    []driver.IndexModel
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewRepository[T Document](collection *driver.Collection, indexes []driver.IndexModel) *Repository[T] {
 | 
				
			||||||
 | 
						return &Repository[T]{collection: collection, indexes: withTimestampIndexes(indexes)}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *Repository[T]) Find(ctx context.Context, filter interface{}, opts ...*options.FindOptions) ([]T, error) {
 | 
				
			||||||
 | 
						cursor, err := s.collection.Find(ctx, filter, opts...)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, repositoryError(s.collection, "Find", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer cursor.Close(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var data []T
 | 
				
			||||||
 | 
						if err := cursor.All(ctx, &data); err != nil {
 | 
				
			||||||
 | 
							return nil, repositoryError(s.collection, "Find", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return data, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *Repository[T]) FindOne(ctx context.Context, filter interface{}, opts ...*options.FindOneOptions) (T, error) {
 | 
				
			||||||
 | 
						var data T
 | 
				
			||||||
 | 
						if err := s.collection.FindOne(ctx, filter, opts...).Decode(&data); err != nil {
 | 
				
			||||||
 | 
							return data, repositoryError(s.collection, "FindOne", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return data, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *Repository[T]) FindByID(ctx context.Context, id string, opts ...*options.FindOneOptions) (T, error) {
 | 
				
			||||||
 | 
						return s.FindOne(ctx, bson.M{"_id": ObjectIDFromHex(id)}, opts...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *Repository[T]) Create(ctx context.Context, data T, opts ...*options.InsertOneOptions) (T, error) {
 | 
				
			||||||
 | 
						data.SetCreatedAt(time.Now().UTC())
 | 
				
			||||||
 | 
						data.SetUpdatedAt(time.Now().UTC())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res, err := s.collection.InsertOne(ctx, data, opts...)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return data, repositoryError(s.collection, "Create", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data.SetID(res.InsertedID.(primitive.ObjectID))
 | 
				
			||||||
 | 
						return data, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *Repository[T]) Update(ctx context.Context, data T, opts ...*options.UpdateOptions) (T, error) {
 | 
				
			||||||
 | 
						return s.UpdateOne(ctx, bson.M{"_id": data.ID()}, data, opts...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *Repository[T]) UpdateOne(ctx context.Context, filter interface{}, data T, opts ...*options.UpdateOptions) (T, error) {
 | 
				
			||||||
 | 
						data.SetUpdatedAt(time.Now().UTC())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err := s.collection.UpdateOne(ctx, filter, bson.M{"$set": data}, opts...); err != nil {
 | 
				
			||||||
 | 
							return data, repositoryError(s.collection, "UpdateOne", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return data, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *Repository[T]) Delete(ctx context.Context, data T, opts ...*options.DeleteOptions) error {
 | 
				
			||||||
 | 
						return s.DeleteOne(ctx, bson.M{"_id": data.ID()}, opts...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *Repository[T]) DeleteOne(ctx context.Context, filter interface{}, opts ...*options.DeleteOptions) error {
 | 
				
			||||||
 | 
						if _, err := s.collection.DeleteOne(ctx, filter, opts...); err != nil {
 | 
				
			||||||
 | 
							return repositoryError(s.collection, "DeleteOne", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *Repository[T]) CountDocuments(ctx context.Context, filter interface{}, opts ...*options.CountOptions) (int, error) {
 | 
				
			||||||
 | 
						count, err := s.collection.CountDocuments(ctx, filter, opts...)
 | 
				
			||||||
 | 
						return int(count), err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *Repository[T]) EnsureIndexes(ctx context.Context, createIndexes bool, logger *zerolog.Logger) error {
 | 
				
			||||||
 | 
						return ensureIndexes(ctx, s.collection, s.indexes, createIndexes, logger)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func repositoryError(collection *driver.Collection, op string, err error) error {
 | 
				
			||||||
 | 
						return fmt.Errorf("%s.%s: %v", collection.Name(), op, err)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user