service backend
This commit is contained in:
78
api/pkg/db/repository/abfilter.go
Normal file
78
api/pkg/db/repository/abfilter.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"github.com/tech/sendico/pkg/db/repository/builder"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
// AccountBoundFilter provides factory methods for creating account-bound filters
|
||||
type AccountBoundFilter struct{}
|
||||
|
||||
// NewAccountBoundFilter creates a new AccountBoundFilter instance
|
||||
func NewAccountBoundFilter() *AccountBoundFilter {
|
||||
return &AccountBoundFilter{}
|
||||
}
|
||||
|
||||
// WithoutOrg creates a filter for account-bound objects without organization filter
|
||||
// This filter finds objects where:
|
||||
// - accountRef matches the provided accountRef, OR
|
||||
// - accountRef is nil/null, OR
|
||||
// - accountRef field doesn't exist
|
||||
func (f *AccountBoundFilter) WithoutOrg(accountRef primitive.ObjectID) builder.Query {
|
||||
return Query().Or(
|
||||
AccountFilter(accountRef),
|
||||
Filter(model.AccountRefField, nil),
|
||||
Exists(AccountField(), false),
|
||||
)
|
||||
}
|
||||
|
||||
// WithOrg creates a filter for account-bound objects with organization filter
|
||||
// This filter finds objects where:
|
||||
// - accountRef matches the provided accountRef, OR
|
||||
// - accountRef is nil/null, OR
|
||||
// - accountRef field doesn't exist
|
||||
// AND combines with organization filter
|
||||
func (f *AccountBoundFilter) WithOrg(accountRef, organizationRef primitive.ObjectID) builder.Query {
|
||||
return Query().And(
|
||||
OrgFilter(organizationRef),
|
||||
f.WithoutOrg(accountRef),
|
||||
)
|
||||
}
|
||||
|
||||
// WithQuery creates a filter for account-bound objects with additional query and organization filter
|
||||
func (f *AccountBoundFilter) WithQuery(accountRef, organizationRef primitive.ObjectID, additionalQuery builder.Query) builder.Query {
|
||||
accountQuery := f.WithOrg(accountRef, organizationRef)
|
||||
return additionalQuery.And(accountQuery)
|
||||
}
|
||||
|
||||
// WithQueryNoOrg creates a filter for account-bound objects with additional query but no org filter
|
||||
func (f *AccountBoundFilter) WithQueryNoOrg(accountRef primitive.ObjectID, additionalQuery builder.Query) builder.Query {
|
||||
accountQuery := f.WithoutOrg(accountRef)
|
||||
return additionalQuery.And(accountQuery)
|
||||
}
|
||||
|
||||
// Global instance for convenience
|
||||
var DefaultAccountBoundFilter = NewAccountBoundFilter()
|
||||
|
||||
// Convenience functions that use the global factory instance
|
||||
|
||||
// WithOrg is a convenience function that uses the default factory
|
||||
func WithOrg(accountRef, organizationRef primitive.ObjectID) builder.Query {
|
||||
return DefaultAccountBoundFilter.WithOrg(accountRef, organizationRef)
|
||||
}
|
||||
|
||||
// WithoutOrg is a convenience function that uses the default factory
|
||||
func WithoutOrg(accountRef primitive.ObjectID) builder.Query {
|
||||
return DefaultAccountBoundFilter.WithoutOrg(accountRef)
|
||||
}
|
||||
|
||||
// WithQuery is a convenience function that uses the default factory
|
||||
func WithQuery(accountRef, organizationRef primitive.ObjectID, additionalQuery builder.Query) builder.Query {
|
||||
return DefaultAccountBoundFilter.WithQuery(accountRef, organizationRef, additionalQuery)
|
||||
}
|
||||
|
||||
// WithQueryNoOrg is a convenience function that uses the default factory
|
||||
func WithQueryNoOrg(accountRef primitive.ObjectID, additionalQuery builder.Query) builder.Query {
|
||||
return DefaultAccountBoundFilter.WithQueryNoOrg(accountRef, additionalQuery)
|
||||
}
|
||||
11
api/pkg/db/repository/builder/accumulator.go
Normal file
11
api/pkg/db/repository/builder/accumulator.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package builder
|
||||
|
||||
import "go.mongodb.org/mongo-driver/bson"
|
||||
|
||||
type Accumulator interface {
|
||||
Build() bson.D
|
||||
}
|
||||
|
||||
type GroupAccumulator interface {
|
||||
Build() bson.D
|
||||
}
|
||||
8
api/pkg/db/repository/builder/alias.go
Normal file
8
api/pkg/db/repository/builder/alias.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package builder
|
||||
|
||||
import "go.mongodb.org/mongo-driver/bson"
|
||||
|
||||
type Alias interface {
|
||||
Field() Field
|
||||
Build() bson.D
|
||||
}
|
||||
7
api/pkg/db/repository/builder/array.go
Normal file
7
api/pkg/db/repository/builder/array.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package builder
|
||||
|
||||
import "go.mongodb.org/mongo-driver/bson"
|
||||
|
||||
type Array interface {
|
||||
Build() bson.A
|
||||
}
|
||||
5
api/pkg/db/repository/builder/expression.go
Normal file
5
api/pkg/db/repository/builder/expression.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package builder
|
||||
|
||||
type Expression interface {
|
||||
Build() any
|
||||
}
|
||||
7
api/pkg/db/repository/builder/field.go
Normal file
7
api/pkg/db/repository/builder/field.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package builder
|
||||
|
||||
type Field interface {
|
||||
Dot(field string) Field
|
||||
CopyWith(field string) Field
|
||||
Build() string
|
||||
}
|
||||
16
api/pkg/db/repository/builder/keyword.go
Normal file
16
api/pkg/db/repository/builder/keyword.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package builder
|
||||
|
||||
type MongoKeyword string
|
||||
|
||||
const (
|
||||
MKAs MongoKeyword = "as"
|
||||
MKForeignField MongoKeyword = "foreignField"
|
||||
MKFrom MongoKeyword = "from"
|
||||
MKIncludeArrayIndex MongoKeyword = "includeArrayIndex"
|
||||
MKLet MongoKeyword = "let"
|
||||
MKLocalField MongoKeyword = "localField"
|
||||
MKPath MongoKeyword = "path"
|
||||
MKPipeline MongoKeyword = "pipeline"
|
||||
MKPreserveNullAndEmptyArrays MongoKeyword = "preserveNullAndEmptyArrays"
|
||||
MKNewRoot MongoKeyword = "newRoot"
|
||||
)
|
||||
57
api/pkg/db/repository/builder/operators.go
Normal file
57
api/pkg/db/repository/builder/operators.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package builder
|
||||
|
||||
type MongoOperation string
|
||||
|
||||
const (
|
||||
// Comparison operators
|
||||
Gt MongoOperation = "$gt"
|
||||
Lt MongoOperation = "$lt"
|
||||
Gte MongoOperation = "$gte"
|
||||
Lte MongoOperation = "$lte"
|
||||
Eq MongoOperation = "$eq"
|
||||
Ne MongoOperation = "$ne"
|
||||
In MongoOperation = "$in"
|
||||
NotIn MongoOperation = "$nin"
|
||||
Exists MongoOperation = "$exists"
|
||||
|
||||
// Logical operators
|
||||
And MongoOperation = "$and"
|
||||
Or MongoOperation = "$or"
|
||||
Not MongoOperation = "$not"
|
||||
|
||||
AddToSet MongoOperation = "$addToSet"
|
||||
Avg MongoOperation = "$avg"
|
||||
Pull MongoOperation = "$pull"
|
||||
Count MongoOperation = "$count"
|
||||
Cond MongoOperation = "$cond"
|
||||
Each MongoOperation = "$each"
|
||||
Expr MongoOperation = "$expr"
|
||||
First MongoOperation = "$first"
|
||||
Group MongoOperation = "$group"
|
||||
IfNull MongoOperation = "$ifNull"
|
||||
Limit MongoOperation = "$limit"
|
||||
Literal MongoOperation = "$literal"
|
||||
Lookup MongoOperation = "$lookup"
|
||||
Match MongoOperation = "$match"
|
||||
Max MongoOperation = "$max"
|
||||
Min MongoOperation = "$min"
|
||||
Push MongoOperation = "$push"
|
||||
Project MongoOperation = "$project"
|
||||
Set MongoOperation = "$set"
|
||||
Inc MongoOperation = "$inc"
|
||||
Unset MongoOperation = "$unset"
|
||||
Rename MongoOperation = "$rename"
|
||||
ReplaceRoot MongoOperation = "$replaceRoot"
|
||||
SetUnion MongoOperation = "$setUnion"
|
||||
Size MongoOperation = "$size"
|
||||
Sort MongoOperation = "$sort"
|
||||
Skip MongoOperation = "$skip"
|
||||
Sum MongoOperation = "$sum"
|
||||
Type MongoOperation = "$type"
|
||||
Unwind MongoOperation = "$unwind"
|
||||
|
||||
Add MongoOperation = "$add"
|
||||
Subtract MongoOperation = "$subtract"
|
||||
Multiply MongoOperation = "$multiply"
|
||||
Divide MongoOperation = "$divide"
|
||||
)
|
||||
16
api/pkg/db/repository/builder/patch.go
Normal file
16
api/pkg/db/repository/builder/patch.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package builder
|
||||
|
||||
import "go.mongodb.org/mongo-driver/bson"
|
||||
|
||||
// Patch defines operations for constructing partial update documents.
|
||||
// Each builder method returns the same Patch instance to allow chaining.
|
||||
type Patch interface {
|
||||
Set(field Field, value any) Patch
|
||||
Inc(field Field, value any) Patch
|
||||
Unset(field Field) Patch
|
||||
Rename(field Field, newName string) Patch
|
||||
Push(field Field, value any) Patch
|
||||
Pull(field Field, value any) Patch
|
||||
AddToSet(field Field, value any) Patch
|
||||
Build() bson.D
|
||||
}
|
||||
24
api/pkg/db/repository/builder/pipeline.go
Normal file
24
api/pkg/db/repository/builder/pipeline.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
)
|
||||
|
||||
type Pipeline interface {
|
||||
Match(filter Query) Pipeline
|
||||
Lookup(from mservice.Type, localField, foreignField, as Field) Pipeline
|
||||
LookupWithPipeline(
|
||||
from mservice.Type,
|
||||
pipeline Pipeline, // your nested pipeline
|
||||
as Field,
|
||||
let *map[string]Field, // optional e.g. {"projRef": Field("$_id")}
|
||||
) Pipeline
|
||||
// unwind with functional options
|
||||
Unwind(path Field, opts ...UnwindOption) Pipeline
|
||||
Count(field Field) Pipeline
|
||||
Group(groupBy Alias, accumulators ...GroupAccumulator) Pipeline
|
||||
Project(projections ...Projection) Pipeline
|
||||
ReplaceRoot(newRoot Expression) Pipeline
|
||||
Build() mongo.Pipeline
|
||||
}
|
||||
7
api/pkg/db/repository/builder/projection.go
Normal file
7
api/pkg/db/repository/builder/projection.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package builder
|
||||
|
||||
import "go.mongodb.org/mongo-driver/bson"
|
||||
|
||||
type Projection interface {
|
||||
Build() bson.D
|
||||
}
|
||||
24
api/pkg/db/repository/builder/query.go
Normal file
24
api/pkg/db/repository/builder/query.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
type Query interface {
|
||||
Filter(field Field, value any) Query
|
||||
And(filters ...Query) Query
|
||||
Or(filters ...Query) Query
|
||||
Expression(value Expression) Query
|
||||
Comparison(field Field, operator MongoOperation, value any) Query
|
||||
RegEx(field Field, pattern, options string) Query
|
||||
In(field Field, values ...any) Query
|
||||
NotIn(field Field, values ...any) Query
|
||||
Sort(field Field, ascending bool) Query
|
||||
Limit(limit *int64) Query
|
||||
Offset(offset *int64) Query
|
||||
Archived(isArchived *bool) Query
|
||||
BuildPipeline() bson.D
|
||||
BuildQuery() bson.D
|
||||
BuildOptions() *options.FindOptions
|
||||
}
|
||||
23
api/pkg/db/repository/builder/unwind.go
Normal file
23
api/pkg/db/repository/builder/unwind.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package builder
|
||||
|
||||
// UnwindOption is a functional option for configuring the $unwind stage.
|
||||
type UnwindOption func(*UnwindOpts)
|
||||
|
||||
type UnwindOpts struct {
|
||||
PreserveNullAndEmptyArrays bool
|
||||
IncludeArrayIndex string
|
||||
}
|
||||
|
||||
// WithPreserveNullAndEmptyArrays tells $unwind to keep docs where the array is null/empty.
|
||||
func WithPreserveNullAndEmptyArrays() UnwindOption {
|
||||
return func(o *UnwindOpts) {
|
||||
o.PreserveNullAndEmptyArrays = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithIncludeArrayIndex adds an array‐index field named idxField to each unwound doc.
|
||||
func WithIncludeArrayIndex(idxField string) UnwindOption {
|
||||
return func(o *UnwindOpts) {
|
||||
o.IncludeArrayIndex = idxField
|
||||
}
|
||||
}
|
||||
5
api/pkg/db/repository/builder/value.go
Normal file
5
api/pkg/db/repository/builder/value.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package builder
|
||||
|
||||
type Value interface {
|
||||
Build() any
|
||||
}
|
||||
273
api/pkg/db/repository/builders.go
Normal file
273
api/pkg/db/repository/builders.go
Normal file
@@ -0,0 +1,273 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"github.com/tech/sendico/pkg/db/internal/mongo/repositoryimp/builderimp"
|
||||
"github.com/tech/sendico/pkg/db/repository/builder"
|
||||
"github.com/tech/sendico/pkg/db/storable"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
func Query() builder.Query {
|
||||
return builderimp.NewQueryImp()
|
||||
}
|
||||
|
||||
func Filter(field string, value any) builder.Query {
|
||||
return Query().Filter(Field(field), value)
|
||||
}
|
||||
|
||||
func Field(baseName string) builder.Field {
|
||||
return builderimp.NewFieldImp(baseName)
|
||||
}
|
||||
|
||||
func Ref(field builder.Field) builder.Field {
|
||||
return builderimp.NewRefFieldImp(field)
|
||||
}
|
||||
|
||||
func RootRef() builder.Field {
|
||||
return builderimp.NewRootRef()
|
||||
}
|
||||
|
||||
func RemoveRef() builder.Field {
|
||||
return builderimp.NewRemoveRef()
|
||||
}
|
||||
|
||||
func Pipeline() builder.Pipeline {
|
||||
return builderimp.NewPipelineImp()
|
||||
}
|
||||
|
||||
func IDField() builder.Field {
|
||||
return Field(storable.IDField)
|
||||
}
|
||||
|
||||
func NameField() builder.Field {
|
||||
return Field(model.NameField)
|
||||
}
|
||||
|
||||
func DescrtiptionField() builder.Field {
|
||||
return Field(model.DescriptionField)
|
||||
}
|
||||
|
||||
func IsArchivedField() builder.Field {
|
||||
return Field(storable.IsArchivedField)
|
||||
}
|
||||
|
||||
func IDFilter(ref primitive.ObjectID) builder.Query {
|
||||
return Query().Filter(IDField(), ref)
|
||||
}
|
||||
|
||||
func ArchivedFilter() builder.Query {
|
||||
return IsArchivedFilter(true)
|
||||
}
|
||||
|
||||
func NotArchivedFilter() builder.Query {
|
||||
return IsArchivedFilter(false)
|
||||
}
|
||||
|
||||
func IsArchivedFilter(isArchived bool) builder.Query {
|
||||
return Query().Filter(IsArchivedField(), isArchived)
|
||||
}
|
||||
|
||||
func OrgField() builder.Field {
|
||||
return Field(storable.OrganizationRefField)
|
||||
}
|
||||
|
||||
func OrgFilter(ref primitive.ObjectID) builder.Query {
|
||||
return Query().Filter(OrgField(), ref)
|
||||
}
|
||||
|
||||
func ProjectField() builder.Field {
|
||||
return Field("projectRef")
|
||||
}
|
||||
|
||||
func ProjectFilter(ref primitive.ObjectID) builder.Query {
|
||||
return Query().Filter(ProjectField(), ref)
|
||||
}
|
||||
|
||||
func AccountField() builder.Field {
|
||||
return Field(model.AccountRefField)
|
||||
}
|
||||
|
||||
func AccountFilter(ref primitive.ObjectID) builder.Query {
|
||||
return Query().Filter(AccountField(), ref)
|
||||
}
|
||||
|
||||
func StatusRefField() builder.Field {
|
||||
return Field("statusRef")
|
||||
}
|
||||
|
||||
func StatusRefFilter(ref primitive.ObjectID) builder.Query {
|
||||
return Query().Filter(StatusRefField(), ref)
|
||||
}
|
||||
|
||||
func PriorityRefField() builder.Field {
|
||||
return Field("priorityRef")
|
||||
}
|
||||
|
||||
func PriorityRefFilter(ref primitive.ObjectID) builder.Query {
|
||||
return Query().Filter(PriorityRefField(), ref)
|
||||
}
|
||||
|
||||
func IndexField() builder.Field {
|
||||
return Field("index")
|
||||
}
|
||||
|
||||
func IndexFilter(index int) builder.Query {
|
||||
return Query().Filter(IndexField(), index)
|
||||
}
|
||||
|
||||
func TagRefsField() builder.Field {
|
||||
return Field(model.TagRefsField)
|
||||
}
|
||||
|
||||
func IndexOpFilter(index int, operation builder.MongoOperation) builder.Query {
|
||||
return Query().Comparison(IndexField(), operation, index)
|
||||
}
|
||||
|
||||
func Patch() builder.Patch {
|
||||
return builderimp.NewPatchImp()
|
||||
}
|
||||
|
||||
func Accumulator(operator builder.MongoOperation, value any) builder.Accumulator {
|
||||
return builderimp.NewAccumulator(operator, value)
|
||||
}
|
||||
|
||||
func GroupAccumulator(field builder.Field, acc builder.Accumulator) builder.GroupAccumulator {
|
||||
return builderimp.NewGroupAccumulator(field, acc)
|
||||
}
|
||||
|
||||
func Literal(value any) builder.Expression {
|
||||
return builderimp.NewLiteralExpression(value)
|
||||
}
|
||||
|
||||
func Projection(alias builder.Alias) builder.Projection {
|
||||
return builderimp.NewAliasProjection(alias)
|
||||
}
|
||||
|
||||
func IncludeField(field builder.Field) builder.Projection {
|
||||
return builderimp.IncludeField(field)
|
||||
}
|
||||
|
||||
func ExcludeField(field builder.Field) builder.Projection {
|
||||
return builderimp.ExcludeField(field)
|
||||
}
|
||||
|
||||
func ProjectionExpr(field builder.Field, expr builder.Expression) builder.Projection {
|
||||
return builderimp.NewProjectionExpr(field, expr)
|
||||
}
|
||||
|
||||
func NullAlias(lhs builder.Field) builder.Alias {
|
||||
return builderimp.NewNullAlias(lhs)
|
||||
}
|
||||
|
||||
func SimpleAlias(lhs, rhs builder.Field) builder.Alias {
|
||||
return builderimp.NewSimpleAlias(lhs, rhs)
|
||||
}
|
||||
|
||||
func ComplexAlias(lhs builder.Field, rhs []builder.Alias) builder.Alias {
|
||||
return builderimp.NewComplexAlias(lhs, rhs)
|
||||
}
|
||||
|
||||
func Aliases(aliases ...builder.Alias) builder.Alias {
|
||||
return builderimp.NewAliases(aliases...)
|
||||
}
|
||||
|
||||
func AddToSet(value builder.Expression) builder.Expression {
|
||||
return builderimp.AddToSet(value)
|
||||
}
|
||||
|
||||
func Size(value builder.Expression) builder.Expression {
|
||||
return builderimp.Size(value)
|
||||
}
|
||||
|
||||
func InRef(value builder.Field) builder.Expression {
|
||||
return builderimp.InRef(value)
|
||||
}
|
||||
|
||||
func In(values ...any) builder.Expression {
|
||||
return builderimp.In(values)
|
||||
}
|
||||
|
||||
func Cond(condition builder.Expression, ifTrue, ifFalse any) builder.Expression {
|
||||
return builderimp.NewCond(condition, ifTrue, ifFalse)
|
||||
}
|
||||
|
||||
func And(exprs ...builder.Expression) builder.Expression {
|
||||
return builderimp.NewAnd(exprs...)
|
||||
}
|
||||
|
||||
func Or(exprs ...builder.Expression) builder.Expression {
|
||||
return builderimp.NewOr(exprs...)
|
||||
}
|
||||
|
||||
func Type(expr builder.Expression) builder.Expression {
|
||||
return builderimp.NewType(expr)
|
||||
}
|
||||
|
||||
func Not(expression builder.Expression) builder.Expression {
|
||||
return builderimp.NewNot(expression)
|
||||
}
|
||||
|
||||
func Sum(expression builder.Expression) builder.Expression {
|
||||
return builderimp.NewSum(expression)
|
||||
}
|
||||
|
||||
func Assign(field builder.Field, expression builder.Expression) builder.Projection {
|
||||
return builderimp.NewAssignment(field, expression)
|
||||
}
|
||||
|
||||
func SetUnion(exprs ...builder.Expression) builder.Expression {
|
||||
return builderimp.NewSetUnion(exprs...)
|
||||
}
|
||||
|
||||
func Eq(left, right builder.Expression) builder.Expression {
|
||||
return builderimp.Eq(left, right)
|
||||
}
|
||||
|
||||
func Gt(left, right builder.Expression) builder.Expression {
|
||||
return builderimp.Gt(left, right)
|
||||
}
|
||||
|
||||
func Lt(left, right builder.Expression) builder.Expression {
|
||||
return builderimp.NewLt(left, right)
|
||||
}
|
||||
|
||||
func Array(expressions ...builder.Expression) builder.Array {
|
||||
return builderimp.NewArray(expressions...)
|
||||
}
|
||||
|
||||
func IfNull(cond, replacement builder.Expression) builder.Expression {
|
||||
return builderimp.NewIfNull(cond, replacement)
|
||||
}
|
||||
|
||||
func Each(exprs ...builder.Expression) builder.Expression {
|
||||
return builderimp.NewEach(exprs...)
|
||||
}
|
||||
|
||||
func Push(expression builder.Expression) builder.Expression {
|
||||
return builderimp.NewPush(expression)
|
||||
}
|
||||
|
||||
func Min(expression builder.Expression) builder.Expression {
|
||||
return builderimp.NewMin(expression)
|
||||
}
|
||||
|
||||
func Ne(left, right builder.Expression) builder.Expression {
|
||||
return builderimp.Ne(left, right)
|
||||
}
|
||||
|
||||
func Compute(field builder.Field, expression builder.Expression) builder.Expression {
|
||||
return builderimp.NewCompute(field, expression)
|
||||
}
|
||||
|
||||
func First(expr builder.Expression) builder.Expression {
|
||||
return builderimp.First(expr)
|
||||
}
|
||||
|
||||
func Value(value any) builder.Value {
|
||||
return builderimp.NewValue(value)
|
||||
}
|
||||
|
||||
func Exists(field builder.Field, exists bool) builder.Query {
|
||||
return Query().Comparison(field, builder.Exists, exists)
|
||||
}
|
||||
19
api/pkg/db/repository/cursor.go
Normal file
19
api/pkg/db/repository/cursor.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"github.com/tech/sendico/pkg/db/repository/builder"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
)
|
||||
|
||||
// ApplyCursor adds pagination and archival filters to the provided query.
|
||||
func ApplyCursor(query builder.Query, cursor *model.ViewCursor) builder.Query {
|
||||
if cursor == nil {
|
||||
return query
|
||||
}
|
||||
|
||||
query = query.Limit(cursor.Limit)
|
||||
query = query.Offset(cursor.Offset)
|
||||
query = query.Archived(cursor.IsArchived)
|
||||
|
||||
return query
|
||||
}
|
||||
5
api/pkg/db/repository/decoder/decoder.go
Normal file
5
api/pkg/db/repository/decoder/decoder.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package repository
|
||||
|
||||
import "go.mongodb.org/mongo-driver/mongo"
|
||||
|
||||
type DecodingFunc = func(r *mongo.Cursor) error
|
||||
93
api/pkg/db/repository/filter_factory_test.go
Normal file
93
api/pkg/db/repository/filter_factory_test.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
func TestAccountBoundFilter_WithOrg(t *testing.T) {
|
||||
factory := NewAccountBoundFilter()
|
||||
accountRef := primitive.NewObjectID()
|
||||
orgRef := primitive.NewObjectID()
|
||||
|
||||
query := factory.WithOrg(accountRef, orgRef)
|
||||
|
||||
// Test that the query is not nil
|
||||
assert.NotNil(t, query)
|
||||
}
|
||||
|
||||
func TestAccountBoundFilter_WithoutOrg(t *testing.T) {
|
||||
factory := NewAccountBoundFilter()
|
||||
accountRef := primitive.NewObjectID()
|
||||
|
||||
query := factory.WithoutOrg(accountRef)
|
||||
|
||||
// Test that the query is not nil
|
||||
assert.NotNil(t, query)
|
||||
}
|
||||
|
||||
func TestAccountBoundFilter_WithQuery(t *testing.T) {
|
||||
factory := NewAccountBoundFilter()
|
||||
accountRef := primitive.NewObjectID()
|
||||
orgRef := primitive.NewObjectID()
|
||||
additionalQuery := Query().Filter(Field("status"), "active")
|
||||
|
||||
query := factory.WithQuery(accountRef, orgRef, additionalQuery)
|
||||
|
||||
// Test that the query is not nil
|
||||
assert.NotNil(t, query)
|
||||
}
|
||||
|
||||
func TestAccountBoundFilter_WithQueryNoOrg(t *testing.T) {
|
||||
factory := NewAccountBoundFilter()
|
||||
accountRef := primitive.NewObjectID()
|
||||
additionalQuery := Query().Filter(Field("status"), "active")
|
||||
|
||||
query := factory.WithQueryNoOrg(accountRef, additionalQuery)
|
||||
|
||||
// Test that the query is not nil
|
||||
assert.NotNil(t, query)
|
||||
}
|
||||
|
||||
func TestDefaultAccountBoundFilter(t *testing.T) {
|
||||
// Test that the default factory is not nil
|
||||
assert.NotNil(t, DefaultAccountBoundFilter)
|
||||
|
||||
// Test that it's the correct type
|
||||
assert.IsType(t, &AccountBoundFilter{}, DefaultAccountBoundFilter)
|
||||
}
|
||||
|
||||
func TestConvenienceFunctions(t *testing.T) {
|
||||
accountRef := primitive.NewObjectID()
|
||||
orgRef := primitive.NewObjectID()
|
||||
additionalQuery := Query().Filter(Field("status"), "active")
|
||||
|
||||
// Test convenience functions
|
||||
query1 := WithOrg(accountRef, orgRef)
|
||||
assert.NotNil(t, query1)
|
||||
|
||||
query2 := WithoutOrg(accountRef)
|
||||
assert.NotNil(t, query2)
|
||||
|
||||
query3 := WithQuery(accountRef, orgRef, additionalQuery)
|
||||
assert.NotNil(t, query3)
|
||||
|
||||
query4 := WithQueryNoOrg(accountRef, additionalQuery)
|
||||
assert.NotNil(t, query4)
|
||||
}
|
||||
|
||||
func TestFilterFactoryConsistency(t *testing.T) {
|
||||
factory := NewAccountBoundFilter()
|
||||
accountRef := primitive.NewObjectID()
|
||||
orgRef := primitive.NewObjectID()
|
||||
|
||||
// Test that factory methods and convenience functions produce the same result
|
||||
query1 := factory.WithOrg(accountRef, orgRef)
|
||||
query2 := WithOrg(accountRef, orgRef)
|
||||
|
||||
// Both should be valid queries
|
||||
assert.NotNil(t, query1)
|
||||
assert.NotNil(t, query2)
|
||||
}
|
||||
21
api/pkg/db/repository/index/index.go
Normal file
21
api/pkg/db/repository/index/index.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package repository
|
||||
|
||||
type Sort int8
|
||||
|
||||
const (
|
||||
Asc Sort = 1
|
||||
Desc Sort = -1
|
||||
)
|
||||
|
||||
type Key struct {
|
||||
Field string
|
||||
Sort Sort // 1 or -1. 0 means “use Type”.
|
||||
Type IndexType // optional: "text", "2dsphere", ...
|
||||
}
|
||||
|
||||
type Definition struct {
|
||||
Keys []Key // mandatory, at least one element
|
||||
Unique bool // unique constraint?
|
||||
TTL *int32 // seconds; nil means “no TTL”
|
||||
Name string // optional explicit name
|
||||
}
|
||||
36
api/pkg/db/repository/index/types.go
Normal file
36
api/pkg/db/repository/index/types.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package repository
|
||||
|
||||
// IndexType represents a supported MongoDB index type.
|
||||
type IndexType string
|
||||
|
||||
const (
|
||||
// IndexTypeNotSet is a default index type
|
||||
IndexTypeNotSet IndexType = ""
|
||||
|
||||
// IndexTypeSingleField is a single-field index.
|
||||
IndexTypeSingleField IndexType = "single"
|
||||
|
||||
// IndexTypeCompound is a compound index on multiple fields.
|
||||
IndexTypeCompound IndexType = "compound"
|
||||
|
||||
// IndexTypeMultikey is an index on array fields (created automatically when needed).
|
||||
IndexTypeMultikey IndexType = "multikey"
|
||||
|
||||
// IndexTypeText is a text index for full-text search.
|
||||
IndexTypeText IndexType = "text"
|
||||
|
||||
// IndexTypeGeo2D is a legacy 2D geospatial index for planar geometry.
|
||||
IndexTypeGeo2D IndexType = "2d"
|
||||
|
||||
// IndexTypeGeo2DSphere is a 2dsphere geospatial index for GeoJSON data.
|
||||
IndexTypeGeo2DSphere IndexType = "2dsphere"
|
||||
|
||||
// IndexTypeHashed is a hashed index for sharding and efficient equality queries.
|
||||
IndexTypeHashed IndexType = "hashed"
|
||||
|
||||
// IndexTypeWildcard is a wildcard index to index all fields or subpaths.
|
||||
IndexTypeWildcard IndexType = "wildcard"
|
||||
|
||||
// IndexTypeClustered is a clustered index that orders the collection on the index key.
|
||||
IndexTypeClustered IndexType = "clustered"
|
||||
)
|
||||
46
api/pkg/db/repository/repository.go
Normal file
46
api/pkg/db/repository/repository.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/tech/sendico/pkg/db/internal/mongo/repositoryimp"
|
||||
"github.com/tech/sendico/pkg/db/repository/builder"
|
||||
rd "github.com/tech/sendico/pkg/db/repository/decoder"
|
||||
ri "github.com/tech/sendico/pkg/db/repository/index"
|
||||
"github.com/tech/sendico/pkg/db/storable"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
)
|
||||
|
||||
type (
|
||||
// FilterQuery selects documents to operate on.
|
||||
FilterQuery = builder.Query
|
||||
// PatchDoc defines field/value modifications for partial updates.
|
||||
PatchDoc = builder.Patch
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
Aggregate(ctx context.Context, builder builder.Pipeline, decoder rd.DecodingFunc) error
|
||||
Insert(ctx context.Context, obj storable.Storable, getFilter builder.Query) error
|
||||
InsertMany(ctx context.Context, objects []storable.Storable) error
|
||||
Get(ctx context.Context, id primitive.ObjectID, result storable.Storable) error
|
||||
FindOneByFilter(ctx context.Context, builder builder.Query, result storable.Storable) error
|
||||
FindManyByFilter(ctx context.Context, builder builder.Query, decoder rd.DecodingFunc) error
|
||||
Update(ctx context.Context, obj storable.Storable) error
|
||||
// Patch applies partial updates defined by patch to the document identified by id.
|
||||
Patch(ctx context.Context, id primitive.ObjectID, patch PatchDoc) error
|
||||
// PatchMany applies partial updates defined by patch to all documents matching filter and returns the number of updated documents.
|
||||
PatchMany(ctx context.Context, filter FilterQuery, patch PatchDoc) (int, error)
|
||||
Delete(ctx context.Context, id primitive.ObjectID) error
|
||||
DeleteMany(ctx context.Context, query builder.Query) error
|
||||
CreateIndex(def *ri.Definition) error
|
||||
ListIDs(ctx context.Context, query builder.Query) ([]primitive.ObjectID, error)
|
||||
ListPermissionBound(ctx context.Context, query builder.Query) ([]model.PermissionBoundStorable, error)
|
||||
ListAccountBound(ctx context.Context, query builder.Query) ([]model.AccountBoundStorable, error)
|
||||
Collection() string
|
||||
}
|
||||
|
||||
func CreateMongoRepository(db *mongo.Database, collection string) Repository {
|
||||
return repositoryimp.NewMongoRepository(db, collection)
|
||||
}
|
||||
Reference in New Issue
Block a user